用于累积字典值的Python生成器表达式

时间:2012-02-14 23:53:31

标签: python dictionary generator

生成器表达式抛弃了大量的元组对,例如。以列表形式:

pairs = [(3, 47), (6, 47), (9, 47), (6, 27), (11, 27), (23, 27), (41, 27), (4, 67), (9, 67), (11, 67), (33, 67)]

对于成对的每一对,使用key = pair [0]和value = pair [1],我想将此对流提供给字典以累积地添加相应键的值。显而易见的解决方案是:

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中。第h

8 个答案:

答案 0 :(得分:6)

为了讨论,这里有一个简单的生成器函数来给我们提供一些数据:

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

但我们可以创建一个协程,它将接受发送给它的每个键值对,并将它们全部累积到传递给它的defaultdict中:

# 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

我们将使用tally defaultdict初始化协程,并通过向其发送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()]

但相反,我们将使用零大小的双端队列来处理所有生成器表达式的值,而不创建一个不必要的临时列表:

# 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/

答案 1 :(得分:4)

您可以使用元组解构和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))

此外,这实际上应该比第一种方法更低效,因为需要为排序创建所有对的内存列表。

答案 2 :(得分:3)

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

答案 3 :(得分:1)

不,如果不使用某些形式的循环,则无法执行此操作。使用for循环确实是最明智的事情,因为你正在修改循环体中的某些东西(而不是,例如,创建一个新的可迭代或列表。)但是,你可以简化代码使用collections.defaultdict,如下所示:

import collections
dict_k_v = collections.defaultdict(int)
for k, v in pairs:
    dict_k_v[k] += v

答案 4 :(得分:1)

Haskell有一个非常好的通用助手:Data.Map的{​​{3}}。

fromListWith类似于Python的dict构造函数,但它还接受一个额外的组合函数来组合重复键的值。将其翻译为Python:

def dict_fromitems(items, combine):
    d = dict()
    for (k, v) in items:
        if k in d:
            d[k] = combine(d[k], v)
        else:
            d[k] = v
    return d

使用这个助手,可以很容易地表达多种组合:

>>> import operator
>>> dict_fromitems(pairs, combine=operator.add)
{33: 67, 3: 47, 4: 67, 6: 74, 9: 114, 11: 94, 41: 27, 23: 27}

>>> dict_fromitems(pairs, combine=min)
{33: 67, 3: 47, 4: 67, 6: 27, 9: 47, 11: 27, 41: 27, 23: 27}

>>> dict_fromitems(pairs, combine=max)
{33: 67, 3: 47, 4: 67, 6: 47, 9: 67, 11: 67, 41: 27, 23: 27}

>>> dict_fromitems(((k, [v]) for (k, v) in pairs), combine=operator.add)
{33: [67], 3: [47], 4: [67], 6: [47, 27], 9: [47, 67], 11: [27, 67], 41: [27], 2
3: [27]}

请注意,与使用defaultdict(int)的解决方案不同,此方法不仅限于数值,如上面的列表示例所示。 (一般来说,任何monoid都是一种有用的可能性:使用union / intersection设置,使用和/或连接的字符串连接,等等。)

<强>附录

正如其他评论所指出的那样,使用循环没有任何问题:它是适当的低级解决方案。但是,如果您可以将低级代码包装在可重用的更高级别的抽象中,那总是很好。

答案 5 :(得分:0)

您可以实现递归调用,但是Python没有针对尾递归进行优化,因此您将支付速度惩罚并且可能会出现“递归深度”异常。

import operator as o
def dict_sum(pairs, totals={}):
  k, v = pairs.pop()
  o.setitem(sum, k, totals.get(k, 0) + v)
  if not pairs:
    return totals
  else:
    return dict_sum(pairs, totals)

我会在for循环中实现它:

import operator as o
totals={}
for k, v in pairs:
   o.setitem(totals, k, totals.get(k, 0) + v)

答案 6 :(得分:0)

为什么不想使用for循环?

pairs = [(3, 47), (6, 47), (9, 47), (6, 27), (11, 27), (23, 27), (41, 27), (4, 67), (9, 67), (11, 67), (33, 67)]
result={}
def add(pair):
    k,v=pair
    result[k]=result.get(k,0)+v
map(add,pairs)
print result

答案 7 :(得分:-2)

类似的东西:

dict_k_v = dict(pairs)