Есть ли лучший, питонический способ сделать это?

Это моя первая программа python -

Требование: прочитайте файл, состоящий из {adId UserId} в каждой строке. Для каждого adId напечатайте количество уникальных пользовательских идентификаторов.

Вот мой код, составленный из чтения документов python. Не могли бы вы дать мне отзыв о том, как я могу написать это более python-ish?

КОД:

import csv

adDict = {}
reader = csv.reader(open("some.csv"), delimiter=' ')
for row in reader:
    adId = row[0]
    userId = row[1]
    if ( adId in adDict ):
        adDict[adId].add(userId)
    else:
        adDict[adId] = set(userId)

for key, value in adDict.items():
    print (key, ',' , len(value))

Спасибо.

Ответ 1

строка кода:

adDict[adId] = set(userId)

вряд ли сделает то, что вы хотите - он будет обрабатывать строку userId как последовательность букв, поэтому, например, если userId был aleax, вы получите набор с четырьмя элементами, как, например, set(['a', 'l', 'e', 'x']). Позже, .add(userId), когда userId будет aleax, снова добавит пятый элемент, строку 'aleax', потому что .add (иначе, чем заданный инициализатор, который принимает итерабельность как свой аргумент) принимает один элемент как его аргумент.

Чтобы создать набор с одним элементом, используйте set([userId]) вместо этого.

Это довольно частая ошибка, поэтому я хотел ее четко объяснить. При этом defaultdict, как было предложено в других ответах, очевидно, является правильным подходом (избегайте setdefault, который никогда не был хорошим дизайном и не имел хорошей производительности, а также был довольно мрачным).

Я бы также избежал любопытного переполнения csv в пользу простого цикла с .split и .strip на каждой строке...

Ответ 2

Поздравляем, ваш код очень приятный. Есть несколько небольших трюков, которые вы могли бы использовать, чтобы сделать их короче/проще.

Существует отличный тип объекта, называемый defaultdict, который предоставляется модулем коллекций. Вместо того, чтобы проверять, имеет ли adDict ключ adId, вы можете установить defaultdict, который действует как обычный dict, за исключением того, что он автоматически предоставляет вам пустой набор(), когда нет ключа. Таким образом, вы можете изменить

if ( adId in adDict ):
    adDict[adId].add(userId)
else:
    adDict[adId] = set(userId)

просто

adDict[adId].add(userId)

Кроме того, вместо

for row in reader:
    adId = row[0]
    userId = row[1]

вы можете сократить это до

for adId,userId in reader:

Изменить:. Как замечает Паркер в комментариях,

for key, value in adDict.iteritems():

- самый эффективный способ итерации над dict, если вы собираетесь использовать оба ключ и значение в цикле. В Python3 вы можете использовать

for key, value in adDict.items():

поскольку items() возвращает итератор.

#!/usr/bin/env python
import csv
from collections import defaultdict

adDict = defaultdict(set)
reader = csv.reader(open("some.csv"), delimiter=' ')
for adId,userId in reader:
    adDict[adId].add(userId)
for key,value in adDict.iteritems():
    print (key, ',' , len(value))

Ответ 3

Вы можете сократить цикл for:

for row in reader:
  adDict.setdefault(row[0], set()).add(row[1])

Ответ 4

Вместо:

for row in reader:
    adId = row[0]
    userId = row[1]

Используйте автоматическую распаковку последовательностей:

for (adId, userId) in reader:

В:

if ( adId in adDict ):

Скобки не нужны.

Вместо:

if ( adId in adDict ):
    adDict[adId].add(userId)
else:
    adDict[adId] = set(userId)

Используйте defaultdict:

from collections import defaultdict
adDict = defaultDict(set)

# ...

adDict[adId].add(userId)

Или, если вам не разрешено использовать другие модули вашим профессором, используйте setdefault():

adDict.setdefault(adId, set()).add(userId)

При печати:

for key, value in adDict.items():
    print (key, ',' , len(value))

Использование форматирования строк может быть проще форматировать:

print "%s,%s" % (key, len(value))

Или, если вы используете Python 3:

print ("{0},{1}".format (key, len(value)))

Ответ 5

Поскольку у вас есть только файл с разделителем пробела, я бы сделал:

from __future__ import with_statement
from collections import defaultdict

ads = defaultdict(set)
with open("some.csv") as f:
    for ad, user in (line.split(" ") for line in f):
        ads[ad].add(user)

for ad in ads:
    print "%s, %s" % (ad, len(ads[ad]))

Ответ 6

Здесь есть несколько отличных ответов.

Один трюк, который мне особенно нравится, - сделать мой код более легким для повторного использования в будущем таким образом

import csv

def parse_my_file(file_name):
     # some existing code goes here
     return aDict

if __name__ == "__main__":
     #this gets executed if this .py file is run directly, rather than imported
     aDict = parse_my_file("some.csv")
     for key, value in adDict.items():
         print (key, ',' , len(value))

Теперь вы можете импортировать ваш парсер csv из другого модуля и получить программный доступ к aDict.

Ответ 7

Единственные изменения, которые я внес бы, - это извлечение сразу нескольких элементов из читателя и использование форматирования строк для операторов печати. ​​

import csv

adDict = {}
reader = csv.reader(open("some.csv"), delimiter=' ')
# Can extract multiple elements from a list in the iteration statement:
for adId, userId in reader: 
    if ( adId in adDict ):
        adDict[adId].add(userId)
    else:
        adDict[adId] = set(userId)

for key, value in adDict.items():
    # I believe this gives you more control over how things are formatted:
    print ("%s, %d" % (key, len(value)))

Ответ 8

Всего несколько бит и частей:

Для извлечения списка строк в переменные:

adId, userId = row

Оператор if не нуждается в фигурных скобках:

if adId in adDict:

Вы можете использовать исключения для обработки отсутствующего ключа в dict, но оба способа работают хорошо, например:

try:
    adDict[adId].add(userId)
except KeyError:
    adDict[adId] = set(userId)