我正在尝试合并来自多个服务器的日志。每个日志都是元组列表(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和优雅的方式,没有这些嵌套的循环。有什么想法吗?
答案 0 :(得分:32)
我认为不会比这更简单:
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
(也称为多重集)是数据最自然的数据结构(元素可以多次出现的一种类型,或者等效地 - 具有语义元素的地图 - > ; 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
:
更新(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))
这只比OP的解决方案慢一点。
基准:http://ideone.com/7IzSx (更新了另一个解决方案,感谢 astynax )
(另外:如果你非常想要一个单行,你可以将updateInPlace
替换为lambda x,y: x.update(y) or x
,它以相同的方式工作,甚至证明是快一秒,但是在可读性。不要: - ))
答案 1 :(得分:8)
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)
<强>输出:强>
Counter({'15.5': 200, '14.5': 100, '16.5': 100, '13.5': 100})
修改强> 正如duncan建议您可以用一行替换这3行:
count = Counter()
for y in inp:
count += Counter(y)
替换为:count = sum((Counter(y) for y in inp), Counter())
答案 2 :(得分: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
使用groupby
代替两个循环和一个defaultdict
将使您的代码更清晰。
答案 3 :(得分:1)
您可以使用Counter或defaultdict,或者您可以尝试我的变体:
def merge_with(d1, d2, fn=lambda x, y: x + y):
res = d1.copy() # "= dict(d1)" for lists of tuples
for key, val in d2.iteritems(): # ".. in d2" for lists of tuples
try:
res[key] = fn(res[key], val)
except KeyError:
res[key] = val
return res
>>> merge_with({'a':1, 'b':2}, {'a':3, 'c':4})
{'a': 4, 'c': 4, 'b': 2}
甚至更通用:
def make_merger(fappend=lambda x, y: x + y, fempty=lambda x: x):
def inner(*dicts):
res = dict((k, fempty(v)) for k, v
in dicts[0].iteritems()) # ".. in dicts[0]" for lists of tuples
for dic in dicts[1:]:
for key, val in dic.iteritems(): # ".. in dic" for lists of tuples
try:
res[key] = fappend(res[key], val)
except KeyError:
res[key] = fempty(val)
return res
return inner
>>> make_merger()({'a':1, 'b':2}, {'a':3, 'c':4})
{'a': 4, 'c': 4, 'b': 2}
>>> appender = make_merger(lambda x, y: x + [y], lambda x: [x])
>>> appender({'a':1, 'b':2}, {'a':3, 'c':4}, {'b':'BBB', 'c':'CCC'})
{'a': [1, 3], 'c': [4, 'CCC'], 'b': [2, 'BBB']}
您也可以继承dict
并实施__add__
方法: