用于累加字典值的Python生成器表达式
生成器表达式会产生很多元组对,比如以列表的形式出现:
pairs = [(3, 47), (6, 47), (9, 47), (6, 27), (11, 27), (23, 27), (41, 27), (4, 67), (9, 67), (11, 67), (33, 67)]
对于每一对元组,键是元组的第一个元素,值是第二个元素。我想把这些元组对放进一个字典里,逐步累加每个键对应的值。显而易见的解决办法是:
dict_k_v = {}
for pair in pairs:
try:
dict_k_v[pair[0]] += pair[1]
except:
dict_k_v[pair[0]] = pair[1]
>>> dict_k_v
{33: 67, 3: 47, 4: 67, 6: 74, 9: 114, 11: 94, 41: 27, 23: 27}
不过,能不能用生成器表达式或者类似的方式来实现,而不使用for循环呢?
编辑
为了更清楚,生成器表达式会产生很多元组对:
(3, 47), (6, 47), (9, 47), (6, 27), (11, 27), (23, 27), (41, 27), (4, 67), (9, 67), (11, 67), (33, 67) ...
我想在生成每一对元组的时候,把它们的键值对累加到一个字典里(可以参考Paul McGuire的回答)。之前的pairs = list[]这句是多余的,抱歉。对于每一对(x,y),x是整数,y可以是整数或者小数。
我的生成器表达式的形式是:
((x,y) for y in something() for x in somethingelse())
我想把每一对(x,y)累加到一个defaultdict里。希望对你有帮助。
8 个回答
>>> dict((x[0], sum(y[1] for y in x[1])) for x in itertools.groupby(sorted(pairs, key=operator.itemgetter(0)), key=operator.itemgetter(0)))
{33: 67, 3: 47, 4: 67, 6: 74, 9: 114, 11: 94, 41: 27, 23: 27}
当然可以!请把你想要翻译的内容发给我,我会帮你把它变得更简单易懂。
你可以用元组解构和一个叫做 defaultdict
的东西来让这个循环变得更简洁:
from collections import defaultdict
d = defaultdict(int)
for k,v in pairs: d[k] += v
这仍然使用了一个for循环,但你不需要处理那些之前没见过的键的情况。我觉得这个方法在可读性和性能上都是最好的选择。
使用 groupby
的概念验证
不过,你 也可以 用 itertools.groupby
来实现,但这有点像是变通的方法:
import itertools
dict((k, sum(v for k,v in group)) for k, group
in itertools.groupby(sorted(pairs), lambda (k,v): k))
而且,这种方法的性能其实应该比第一种差,因为需要创建一个包含所有对的内存列表来进行排序。
为了讨论,这里有一个简单的生成器函数,可以给我们提供一些数据:
from random import randint
def generator1():
for i in range(10000):
yield (randint(1,10), randint(1,100))
接下来,这是一个基本的解决方案,使用Python的for循环来处理生成器,并统计每个键值对的数量:
from collections import defaultdict
tally = defaultdict(int)
for k,v in generator1():
tally[k] += v
for k in sorted(tally):
print k, tally[k]
它会打印出类似这样的内容:
1 49030
2 51963
3 51396
4 49292
5 51908
6 49481
7 49645
8 49149
9 48523
10 50722
但是我们可以创建一个协程,它会接受每个发送给它的键值对,并把它们都累积到一个传入的默认字典中:
# define coroutine to update defaultdict for every
# key,value pair sent to it
def tallyAccumulator(t):
try:
while True:
k,v = (yield)
t[k] += v
except GeneratorExit:
pass
我们会用一个统计的默认字典来初始化这个协程,并通过发送一个None值来准备好接受值:
# init coroutine
tally = defaultdict(int)
c = tallyAccumulator(tally)
c.send(None)
我们可以使用for循环或者列表推导式将所有生成器的值发送到协程:
for val in generator1():
c.send(val)
或者
[c.send(val) for val in generator1()]
但我们会使用一个零大小的双端队列(deque)来处理所有生成器表达式的值,而不需要创建一个不必要的包含None的临时列表:
# create generator expression consumer
from collections import deque
do_all = deque(maxlen=0).extend
# loop thru generator at C speed, instead of Python for-loop speed
do_all(c.send(val) for val in generator1())
现在我们再看看这些值:
for k in sorted(tally):
print k, tally[k]
我们得到的列表和第一个类似:
1 52236
2 49139
3 51848
4 51194
5 51275
6 50012
7 51875
8 46013
9 50955
10 52192
想了解更多关于协程的内容,可以访问David Beazley的网站:http://www.dabeaz.com/coroutines/