Добавление новых ключей в словарь при увеличении существующих значений
Я обрабатываю 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
Мой вопрос: какой способ более эффективен... чище... лучше... и т.д. Или есть лучший способ. Оба способа работают и дают один и тот же ответ, но я думал, что я буду использовать ум улья в качестве учебного примера.
Спасибо -
Ответы
Ответ 1
Используйте 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, используйте defaultdict
.
from collections import defaultdict
count = defaultdict(int)
for x in my_csv_reader:
count[x[3]] += 1
Ответ 2
Вы спросили, что было более эффективным. Предполагая, что вы говорите о скорости выполнения: если ваши данные малы, это не имеет значения. Если он большой и типичный, случай "уже существует" будет происходить гораздо чаще, чем случай "не в dict". Это наблюдение объясняет некоторые результаты.
Ниже приведен код, который можно использовать с модулем 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 (iterable)? (I имеют python 2.6 и не могут его протестировать.) "" "
tally6: просто делает d = Count(iterable); return d
, занимает 60,0 мс
Вы можете посмотреть исходный код (collection.py в репозитории SVN)... вот что делает мой Python27\Lib\collections.py
, когда iterable
не является экземпляром сопоставления:
self_get = self.get
for elem in iterable:
self[elem] = self_get(elem, 0) + 1
Видел этот код где-нибудь раньше? Там очень много переносов, чтобы вызвать код, который можно запустить в Python 1.5.2 :-O
Ответ 3
from collections import Counter
Counter(a)
Ответ 4
Поскольку у вас нет доступа к Counter, ваш лучший выбор - ваш третий подход. Это намного чище и легче читать. Кроме того, у него нет вечного тестирования (и ветвления), которое имеет первые два подхода, что делает его более эффективным.
Ответ 5
Используйте setdefault
.
a[x[3]] = a.setdefault(x[3], 0) + 1
setdefault
получает значение указанного ключа (x[3]
в этом случае), или если оно не существует, указанное значение (0
в этом случае).