python直方图单行

python histogram one-liner

编写计算柱状图的python程序有很多方法。

我所说的柱状图,是指一个计算iterable中对象出现次数并在字典中输出计数的函数。例如:

1
2
3
>>> L = 'abracadabra'
>>> histogram(L)
{'a': 5, 'b': 2, 'c': 1, 'd': 1, 'r': 2}

编写此函数的一种方法是:

1
2
3
4
5
6
7
8
def histogram(L):
    d = {}
    for x in L:
        if x in d:
            d[x] += 1
        else:
            d[x] = 1
    return d

写这个函数有更简洁的方法吗?

如果我们有关于python的字典理解,我们可以写:

1
>>> { x: L.count(x) for x in set(L) }

但是由于python 2.6没有它们,我们必须写:

1
>>> dict([(x, L.count(x)) for x in set(L)])

尽管这种方法可能是可读的,但它是不有效的:l被遍历多次。此外,对于单寿命生成器,这不起作用;对于迭代器生成器,该函数应该同样有效,例如:

1
2
3
def gen(L):
    for x in L:
        yield x

我们可以尝试使用reduce函数(r.i.p.):

1
>>> reduce(lambda d,x: dict(d, x=d.get(x,0)+1), L, {}) # wrong!

哎呀,这不管用:密钥名是'x',不是x。:(

我的结尾是:

1
>>> reduce(lambda d,x: dict(d.items() + [(x, d.get(x, 0)+1)]), L, {})

(在python 3中,我们必须编写list(d.items())而不是d.items(),但这是假设的,因为没有reduce)。

请用更好、更易读的一行字打败我!;)


python 3.x有reduce,你只需要做from functools import reduce。它还具有"dict-understructions",这与示例中的语法完全相同。

python 2.7和3.x也有一个计数器类,它可以完全满足您的需要:

1
2
from collections import Counter
cnt = Counter("abracadabra")

在python 2.6或更早版本中,我个人会使用一个defaultdict并用两行来完成它:

1
2
d = defaultdict(int)
for x in xs: d[x] += 1

这是一种干净、高效、脓毒症,对大多数人来说,这比任何涉及到reduce的东西都容易理解。


为OneLines导入模块有点欺骗,所以这里有一个OneLiner,它是O(N),至少可以追溯到python2.4。

1
2
3
>>> 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}

如果你认为__方法很糟糕,你可以一直这样做。

1
2
3
>>> 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}

:)


1
2
3
import pandas as pd

pd.Series(list(L)).value_counts()


1
$d{$_} += 1 for split //, 'abracadabra';


对于Python2.7,您可以使用下面的小列表理解:

1
2
v = list('abracadabra')
print {x: v.count(x) for x in set(v)}


一个可以追溯到2.3(比Timmerman的稍短,我认为可读性更强):

1
2
3
4
5
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}


我需要一个柱状图实现来在Python2.2到2.7版本中工作,并得出了以下结论:

1
2
3
4
5
>>> 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}

我被伊莱·考特赖特的一篇不守规矩的口述所启发。这些是在python 2.5中引入的,因此不能使用。但是可以使用dict.setdefault(key,default)来模拟它们。

这基本上是相同的事情,gnibble正在做,但我必须先写这之前,我可以完全理解他的lambda函数。


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

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

当然,这并不能打败现有的解决方案(也不能在速度上,也不能在脓毒症上),但作为交换,你已经得到了一个很好的、纯功能的代码片段。顺便说一句,如果python有一个方法dict.merge(),这会更漂亮一些。


有一段时间,任何使用itertools的东西都是从定义上讲的Python。不过,这有点不透明:

1
2
3
4
5
>>> 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。