Python直方图一行
编写计算直方图的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
有没有更简洁的方式来编写这个function?
如果我们有Python的字典parsing,我们可以写:
>>> { 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()
,但是它是d.items()
的,因为在那里没有reduce
。
请用更好,更具可读性的单行纸打败我! ;)
Python 3.x确实reduce
,你只需from functools import reduce
做一个。 它也有“字典理解”,在你的例子中有正确的语法。
Python 2.7和3.x也有一个Counter类,它正是你想要的:
from collections import Counter cnt = Counter("abracadabra")
在Python 2.6或更早版本中,我个人使用defaultdict ,并在两行中执行:
d = defaultdict(int) for x in xs: d[x] += 1
这是干净的,高效的,Pythonic,对于大多数人来说,比任何涉及reduce
事情都容易理解。
导入模块的内容是非常不好的,所以这里有一个O(n)的oneliner,至less可以在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}
🙂
$d{$_} += 1 for split //, 'abracadabra';
对于Python 2.7,你可以使用这个小列表理解:
v = list('abracadabra') print {x: v.count(x) for x in set(v)}
import pandas as pd pd.Series(list(L)).value_counts()
一个回到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}
有一段时间,任何使用itertools
东西都是由Pythonic定义的。 尽pipe如此,这在不透明的一面是有点儿的:
>>> 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。
你使用reduce
单线程几乎没问题,你只需稍微调整一下即可:
>>> reduce(lambda d, x: dict(d, **{x: d.get(x, 0) + 1}), L, {}) {'a': 5, 'b': 2, 'c': 1, 'd': 1, 'r': 2}
当然,这不会殴打就地解决scheme(速度和pythonicity),但作为交换,你已经得到了一个不错的纯粹的function片段。 顺便说一句,如果Python有一个方法dict.merge()
,这将会更漂亮dict.merge()
。
我需要一个直方图实现工作在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的defaultdict的启发。 这些是在Python 2.5中引入的,所以不能使用。 但是它们可以用dict.setdefault(key,default)来模拟。
这与gibibbler基本上是一样的,但是在我完全理解他的lambda函数之前,我必须先写这个。