Python:优雅地将字典与值的sum()合并

Ada*_*tan 30 python dictionary

我正在尝试合并来自多个服务器的日志.每个日志都是元组(date,count)列表.date可能会出现不止一次,我希望生成的字典保存所有服务器的所有计数的总和.

这是我的尝试,例如一些数据:

from collections import defaultdict

a=[("13.5",100)]
b=[("14.5",100), ("15.5", 100)]
c=[("15.5",100), ("16.5", 100)]
input=[a,b,c]

output=defaultdict(int)
for d in input:
        for item in d:
           output[item[0]]+=item[1]
print dict(output)
Run Code Online (Sandbox Code Playgroud)

这使:

{'14.5': 100, '16.5': 100, '13.5': 100, '15.5': 200}
Run Code Online (Sandbox Code Playgroud)

正如所料.

因为看到代码的同事,我准备去香蕉了.她坚持认为必须有一个更加Pythonic和优雅的方式,没有这些嵌套的循环.有任何想法吗?

Kos*_*Kos 33

我认为不会比这更简单:

a=[("13.5",100)]
b=[("14.5",100), ("15.5", 100)]
c=[("15.5",100), ("16.5", 100)]
input=[a,b,c]

from collections import Counter

print sum(
    (Counter(dict(x)) for x in input),
    Counter())
Run Code Online (Sandbox Code Playgroud)

请注意Counter(也称为多集)是数据最自然的数据结构(元素可以多次出现的集合类型,或者等效地 - 具有语义元素的映射 - > OccurrenceCount.您可以在其中使用它第一个地方,而不是元组列表.


也可能:

from collections import Counter
from operator import add

print reduce(add, (Counter(dict(x)) for x in input))
Run Code Online (Sandbox Code Playgroud)

使用reduce(add, seq)而不是sum(seq, initialValue)通常更灵活,并允许您跳过传递冗余的初始值.

请注意,您还可以使用operator.and_查找多重集的交集而不是总和.


上面的变体非常慢,因为每一步都会创建一个新的Counter.我们来解决这个问题.

我们知道Counter+Counter返回Counter带有合并数据的新内容.这没关系,但我们希望避免额外的创建.让我们Counter.update改用:

update(self,iterable = None,**kwds)unbound collections.Counter方法

与dict.update()类似,但添加计数而不是替换它们.Source可以是可迭代的,字典或其他Counter实例.

这就是我们想要的.让我们用兼容的函数包装它,reduce看看会发生什么.

def updateInPlace(a,b):
    a.update(b)
    return a

print reduce(updateInPlace, (Counter(dict(x)) for x in input))
Run Code Online (Sandbox Code Playgroud)

这只比OP的解决方案略慢.

基准:http ://ideone.com/7IzSx(更新了另一个解决方案,感谢astynax)

(另外:如果你拼命想要的一行代码,您可以替换updateInPlace通过lambda x,y: x.update(y) or x其工作方式相同,甚至被证明是一个分裂的第二快,但是在可读性失败时不:-)).

  • +1我真的很喜欢这个解决方案. (2认同)

Ash*_*ary 9

from collections import Counter


a = [("13.5",100)]
b = [("14.5",100), ("15.5", 100)]
c = [("15.5",100), ("16.5", 100)]

inp = [dict(x) for x in (a,b,c)]
count = Counter()
for y in inp:
  count += Counter(y)
print(count)
Run Code Online (Sandbox Code Playgroud)

输出:

Counter({'15.5': 200, '14.5': 100, '16.5': 100, '13.5': 100})
Run Code Online (Sandbox Code Playgroud)

编辑: 正如邓肯建议你可以用一行替换这3行:

   count = Counter()
    for y in inp:
      count += Counter(y)
Run Code Online (Sandbox Code Playgroud)

替换为: count = sum((Counter(y) for y in inp), Counter())

  • 您甚至可以使用`sum`删除`for`循环:`count = sum((在inp中为y的计数器(y)),Counter())` (3认同)

slo*_*oth 7

你可以使用itertools的groupby:

from itertools import groupby, chain

a=[("13.5",100)]
b=[("14.5",100), ("15.5", 100)]
c=[("15.5",100), ("16.5", 100)]
input = sorted(chain(a,b,c), key=lambda x: x[0])

output = {}
for k, g in groupby(input, key=lambda x: x[0]):
  output[k] = sum(x[1] for x in g)

print output
Run Code Online (Sandbox Code Playgroud)

使用groupby而不是两个循环和一个defaultdict将使您的代码更清晰.

  • 而不是lambda,你也可以放入一个`operator.itemgetter(0)`:) (2认同)