47

有很多方法可以编写计算直方图的 Python 程序。

通过直方图,我的意思是一个函数,它计算对象在 an 中的出现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函数(RIP):

>>> 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(),但这是假设的,因为那里没有reduce。)

请用更好,更易读的单行符击败我!;)

4

9 回答 9

76

Python 3.x 确实有reduce,你只需要做一个from functools import reduce. 它还具有“dict理解”,它具有您示例中的语法。

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

这是干净、高效、Pythonic 的,并且对于大多数人来说比任何涉及reduce.

于 2010-05-20T01:33:21.733 回答
7

为 oneliner 导入模块有点作弊,所以这里有一个 O(n) 并且至少可以追溯到 Python2.4 的 oneliner

>>> 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}

:)

于 2010-08-18T04:47:10.337 回答
6
import pandas as pd

pd.Series(list(L)).value_counts()
于 2015-02-20T15:18:39.707 回答
6
$d{$_} += 1 for split //, 'abracadabra';
于 2010-11-18T02:33:41.743 回答
5

对于 python 2.7,您可以使用这个小列表推导:

v = list('abracadabra')
print {x: v.count(x) for x in set(v)}
于 2013-08-15T21:44:45.963 回答
4

一个可以回溯到 2.3(比 Timmerman 的略短,我认为更具可读性):

L = 'abracadabra'
hist = {}
for x in L: hist[x] = hist.pop(x,0) + 1
print hist
{'a': 5, 'r': 2, 'b': 2, 'c': 1, 'd': 1}
于 2012-12-13T19:04:09.003 回答
1

有一段时间,任何使用的东西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。

于 2010-05-20T02:21:01.370 回答
1

你的单线使用reduce几乎没问题,你只需要稍微调整一下:

>>> reduce(lambda d, x: dict(d, **{x: d.get(x, 0) + 1}), L, {})
{'a': 5, 'b': 2, 'c': 1, 'd': 1, 'r': 2}

当然,这不会胜过就地解决方案(无论是速度还是pythonicity),但作为交换,您会得到一个不错的纯功能片段。顺便说一句,如果 Python 有一个方法,这会更漂亮一些dict.merge()

于 2010-09-06T14:58:05.657 回答
1

我需要一个直方图实现才能在 python 2.2 到 2.7 中工作,并想出了这个:

>>> L = 'abracadabra'
>>> hist = {}
>>> for x in L: hist[x] = hist.setdefault(x,0)+1
>>> print hist
{'a': 5, 'r': 2, 'b': 2, 'c': 1, 'd': 1}

伊莱·考特赖特(Eli Courtwright)关于缺席判决的帖子启发了我。这些是在 python 2.5 中引入的,所以不能使用。但是可以使用 dict.setdefault(key,default) 来模拟它们。

这与 gnibbler 所做的基本相同,但我必须先写这个,然后才能完全理解他的 lambda 函数。

于 2012-02-21T16:24:59.730 回答