我正在尝试合并来自多台服务器的日志。每个日志都是一个元组列表(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)
这给出了:
{'14.5': 100, '16.5': 100, '13.5': 100, '15.5': 200}
正如预期的那样。
我要疯了,因为一位同事看到了代码。她坚持认为,必须有一种更加 Pythonic 和优雅的方式来完成它,而无需这些嵌套的 for 循环。有什么想法吗?
最佳答案
我认为没有比这更简单的了:
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())
请注意,Counter
(也称为多重集)是数据最自然的数据结构(一种元素可以多次属于的集合类型,或等价的 - 具有语义的映射Element -> OccurrenceCount。你可以一开始就使用它,而不是元组列表。
也可以:
from collections import Counter
from operator import add
print reduce(add, (Counter(dict(x)) for x in input))
使用 reduce(add, seq)
而不是 sum(seq, initialValue)
通常更灵活,并且允许您跳过传递多余的初始值。
请注意,您也可以使用 operator.and_
来查找多重集合的交集而不是求和。
上述变体非常慢,因为每一步都会创建一个新的计数器。让我们解决这个问题。
我们知道 Counter+Counter
返回一个带有合并数据的新 Counter
。这没关系,但我们想避免额外的创建。让我们使用 Counter.update
代替:
update(self, iterable=None, **kwds) unbound collections.Counter method
Like dict.update() but add counts instead of replacing them. Source can be an iterable, a dictionary, or another Counter instance.
这就是我们想要的。让我们用兼容 reduce
的函数来包装它,看看会发生什么。
def updateInPlace(a,b):
a.update(b)
return a
print reduce(updateInPlace, (Counter(dict(x)) for x in input))
这仅比 OP 的解决方案慢一点。
基准测试:http://ideone.com/7IzSx (感谢 astynax,更新了另一个解决方案)
(另外:如果你非常想要一个单行,你可以用 lambda x,y: x.update(y) 或 x
替换 updateInPlace
它的工作方式相同,甚至被证明更快,但在可读性方面失败。不要:-))
关于Python:优雅地将字典与值的 sum() 合并,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11290092/