Добавлять новые ключи в словарь, увеличивая при этом существующие значения - PullRequest
8 голосов
/ 27 октября 2010

Я обрабатываю файл CSV и подсчитываю уникальные значения столбца 4. До сих пор я кодировал эти три способа. Один использует «если ключ в словаре», второй перехватывает KeyError, а третий использует «DefaultDictionary». Например (где x [3] - значение из файла, а «a» - словарь):

Первый способ:

if x[3] in a:
    a[x[3]] += 1
else:
    a[x[3]] = 1

Второй способ:

try:
    b[x[3]] += 1
except KeyError:
    b[x[3]] = 1

Третий способ:

from collections import defaultdict
c = defaultdict(int)
c[x[3]] += 1

У меня вопрос: какой путь более эффективен ... чище ... лучше ... и т. Д. Или есть лучший способ. Оба способа работают и дают один и тот же ответ, но я подумал, что смогу использовать улей в качестве учебного примера.

Спасибо -

Ответы [ 5 ]

6 голосов
/ 28 октября 2010

Вы спросили, что было более эффективным.Предполагая, что вы говорите о скорости выполнения: если ваши данные невелики, это не имеет значения.Если он большой и типичный, дело «уже существует» будет происходить гораздо чаще, чем дело «не в диктате».Это наблюдение объясняет некоторые результаты.

Ниже приведен код, который можно использовать с модулем timeit для исследования скорости без дополнительных затрат на чтение файла.Я взял на себя смелость добавить 5-й метод, который не является неконкурентным и будет работать на любом Python с версии не ниже 1.5.2 [проверено].

from collections import defaultdict, Counter

def tally0(iterable):
    # DOESN'T WORK -- common base case for timing
    d = {}
    for item in iterable:
        d[item] = 1
    return d

def tally1(iterable):
    d = {}
    for item in iterable:
        if item in d:
            d[item] += 1
        else:
            d[item] = 1
    return d

def tally2(iterable):
    d = {}
    for item in iterable:
        try:
            d[item] += 1
        except KeyError:
            d[item] = 1
    return d

def tally3(iterable):
    d = defaultdict(int)
    for item in iterable:
        d[item] += 1

def tally4(iterable):
    d = Counter()
    for item in iterable:
        d[item] += 1

def tally5(iterable):
    d = {}
    dg = d.get
    for item in iterable:
        d[item] = dg(item, 0) + 1
    return d

Обычный запуск (в Windows XP "Командная строка"окно":

prompt>\python27\python -mtimeit -s"t=1000*'now is the winter of our discontent made glorious summer by this son of york';import tally_bench as tb" "tb.tally1(t)"
10 loops, best of 3: 29.5 msec per loop

Вот результаты (мсек на цикл):

0 base case   13.6
1 if k in d   29.5
2 try/except  26.1
3 defaultdict 23.4
4 Counter     79.4
5 d.get(k, 0) 29.2

Еще одно испытание по времени:

prompt>\python27\python -mtimeit -s"from collections import defaultdict;d=defaultdict(int)" "d[1]+=1"
1000000 loops, best of 3: 0.309 usec per loop

prompt>\python27\python -mtimeit -s"from collections import Counter;d=Counter()" "d[1]+=1"
1000000 loops, best of 3: 1.02 usec per loop

Скорость Counter возможно, из-за того, что он частично реализован в коде Python, тогда как defaultdict полностью в C (по крайней мере, в 2.7).

Обратите внимание, что Counter() НЕ является просто "синтаксическим сахаром" для defaultdict(int)- он реализует полный bag aka multiset объект - подробности см. в документации;они могут спасти вас от повторного изобретения колеса, если вам нужна какая-то необычная постобработка.Если все, что вы хотите сделать, это считать вещи, используйте defaultdict.

Обновление в ответ на вопрос @Steven Rumbalski: "" "Мне интересно, что произойдет, если выпереместите итерируемое в конструктор Counter: d = Counter (итерируемый)? (у меня есть python 2.6, и я не могу его протестировать.) "" "

tally6: просто делает d = Count(iterable); return d, занимает 60,0 мсек

Вы можете посмотреть на источник (collection.py в репозитории SVN) ... вот что делает мой Python27\Lib\collections.py, когда iterable не является экземпляром Mapping:

            self_get = self.get
            for elem in iterable:
                self[elem] = self_get(elem, 0) + 1

Видел этот код где угоднодо?Существует много увлечений только для вызова кода, который работает в Python 1.5.2 :-O

6 голосов
/ 27 октября 2010

Используйте collections.Counter. Counter является синтаксическим сахаром для defaultdict(int), но что здорово, так это то, что он принимает итератор в конструкторе, тем самым сохраняя дополнительный шаг (я предполагаю, что все ваши примеры выше обернуты в цикл for).

from collections import Counter
count = Counter(x[3] for x in my_csv_reader)

До введения collections.Counter, collections.defaultdict был наиболее идиоматическим для этой задачи, поэтому для пользователей <2.7, используйте <code>defaultdict.

from collections import defaultdict
count = defaultdict(int)
for x in my_csv_reader:
    count[x[3]] += 1
1 голос
/ 27 октября 2010
from collections import Counter
Counter(a)
0 голосов
/ 28 октября 2010

Использование setdefault.

a[x[3]] = a.setdefault(x[3], 0) + 1

setdefault возвращает значение указанного ключа (в данном случае x[3]) или, если оно не существует, указанное значение (0в данном случае).

0 голосов
/ 27 октября 2010

Поскольку у вас нет доступа к Counter , лучшим вариантом будет ваш третий подход.Это намного чище и легче для чтения.Кроме того, он не имеет постоянного тестирования (и ветвления), который есть у первых двух подходов, что делает его более эффективным.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...