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

用于累积字典值的Python生成器表达式,python,dictionary,generator,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:

生成器表达式正在抛出大量元组对,例如列表形式:

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中。Hth.

您可以使用元组分解和a来大大缩短该循环:

from collections import defaultdict
d = defaultdict(int)
for k,v in pairs: d[k] += v
这仍然使用for循环,但您不必处理以前未看到密钥的情况。我认为这可能是最好的解决方案,无论是可读性还是性能

使用
groupby
也就是说,你可以用它来做,但这有点像黑客:

导入itertools
dict((k,sum(v代表k,v代表组))代表k,组
在itertools.groupby中(排序(对),lambda(k,v):k))

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

不,如果不使用某种形式的循环,就不能这样做。使用
for
循环确实是最明智的做法,因为您正在修改循环体中的某些内容(而不是创建新的iterable或list)。但是,您可以通过使用
collections.defaultdict
来简化代码,如下所示:

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

dict_k_v = dict(pairs)
1 49030
2 51963
3 51396
4 49292
5 51908
6 49481
7 49645
8 49149
9 48523
10 50722

您可以实现递归调用,但是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)

为什么不想使用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

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

from random import randint
def generator1():
    for i in range(10000):
        yield (randint(1,10), randint(1,100))
这是一个基本的解决方案,它使用PythonFor循环来使用生成器,并统计每个键值对的计数

from collections import defaultdict

tally = defaultdict(int)
for k,v in generator1():
    tally[k] += v

for k in sorted(tally):
    print k, tally[k]
将打印如下内容:

dict_k_v = dict(pairs)
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)

但是,我们将使用大小为零的deque来处理生成器表达式的所有值,而不创建不必要的临时无值列表:

# 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的页面上阅读更多关于协同程序的信息:

Haskell有一个非常好的通用帮助程序:
Data.Map

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)
的解决方案不同,此方法不限于数值,如上面的列表示例所示。(通常,任何幺半群都是一种有用的可能性:具有并集/交集的集合、具有和/或的布尔、具有串联的字符串,等等。)

附录


正如其他评论所指出的,为此使用循环没有什么错:它是适当的低级解决方案。但是,如果您可以将低级代码封装在可重用的高级抽象中,这总是好的。

对于重复的键来说,这不是正确的做法。(请不要删除答案,否则我们会让更多人建议。)默认情况下,
sorted
不应该按字典进行比较吗?@Niklas:当然。但是我不关心第二个元素,所以我把它放在一边,这样它就不会被排序了。@IgnacioVazquez Abrams你的解决方案又短又甜,性能很好,并且适合我的其余代码。因为我要求提供一个字典解决方案,在生成对时将它们累加起来,所以接受您的解决方案是不公平的。很抱歉鉴于OP声明他们正在使用“生成器表达式”和“大量”对,我支持defaultdict解决方案,而不是任何sort+groupby解决方案,因为for循环干净地处理对流,将总数汇总到defaultdict的条目中,并没有需要创建内存列表中的值(如通过排序内部完成的那样)。@保罗:这正是我的观点,但是回过头来看这个答案,我认为用< <代码> > < /C> >循环不是很明显。将累加器包装到defaultdict中的for循环是最干净的解决方案。我刚刚就推入dict的所有选项进行了长时间的讨论,结果表明,编写此代码的最有效方法是使用if key in dict:/else:(并非您想要使用for循环:-)@PaulMcGuire当数据集非常大和/或操作正在连续执行时,for循环的主要厌恶因素是可能的性能影响。一个选择是Cython,但我想看看