Существует много способов записать программу Python, которая вычисляет гистограмму.
Гистограммой я имею в виду функцию, которая включает возникновение объектов iterable
и выводы количества в словаре. Например:
>>> L = 'abracadabra'
>>> histogram(L)
{'a': 5, 'b': 2, 'c': 1, 'd': 1, 'r': 2}
Один способ записать эту функцию:
def histogram(L):
d = {}
for x in L:
if x in d:
d[x] += 1
else:
d[x] = 1
return d
Есть ли более краткие способы записать эту функцию?
Если бы у нас были понимания словаря в Python, то мы могли бы записать:
>>> { x: L.count(x) for x in set(L) }
но так как Python 2.6 не имеет их, мы должны записать:
>>> dict([(x, L.count(x)) for x in set(L)])
Хотя этот подход может быть читаемым, это не эффективно: L обойден - через многократно. Кроме того, это не будет работать на пожизненные генераторы; функция должна работать одинаково хорошо на генераторы итератора, такие как:
def gen(L):
for x in L:
yield x
Мы могли бы попытаться использовать reduce
функция (R.I.P).:
>>> reduce(lambda d,x: dict(d, x=d.get(x,0)+1), L, {}) # wrong!
Ой, это не работает: ключевое имя 'x'
, нет x
. :(
Я закончил:
>>> reduce(lambda d,x: dict(d.items() + [(x, d.get(x, 0)+1)]), L, {})
(В Python 3 должны были бы записать мы list(d.items())
вместо d.items()
, но это - hypothethical, так как существует нет reduce
там.)
Победите меня лучшим, большим количеством читаемой остроты!;)
Python 3.x действительно имеет reduce
, вам просто нужно сделать из functools import reduce
. У него также есть «понимание слов», синтаксис которых точно такой же, как в вашем примере.
Python 2.7 и 3.x также имеют класс Counter , который делает именно то, что вы хотите:
from collections import Counter
cnt = Counter("abracadabra")
В Python 2.6 или более ранних версиях я бы лично использовал defaultdict и сделайте это в 2 строчки:
d = defaultdict(int)
for x in xs: d[x] += 1
Это чисто, эффективно, на питоническом языке и намного проще для понимания, чем что-либо, связанное с reduce
.
Какое-то время все, что использует itertools
, было по определению Pythonic. Тем не менее, это немного непрозрачно:
>>> from itertools import groupby
>>> grouplen = lambda grp : sum(1 for i in grp)
>>> hist = dict((a[0], grouplen(a[1])) for a in groupby(sorted("ABRACADABRA")))
>>> print hist
{'A': 5, 'R': 2, 'C': 1, 'B': 2, 'D': 1}
В настоящее время я использую Python 2.5.4.
Импортировать модули для oneliner'ов вроде бы хитро, так что вот однострочный O (n), работающий, по крайней мере, еще в Python2.4
>>> f=lambda s,d={}:([d.__setitem__(i,d.get(i,0)+1) for i in s],d)[-1]
>>> f("ABRACADABRA")
{'A': 5, 'R': 2, 'B': 2, 'C': 1, 'D': 1}
И если вы думаете __
методы хакерские, вы всегда можете сделать это
>>> f=lambda s,d=lambda:0:vars(([setattr(d,i,getattr(d,i,0)+1) for i in s],d)[-1])
>>> f("ABRACADABRA")
{'A': 5, 'R': 2, 'B': 2, 'C': 1, 'D': 1}
:)