我想合并多个collections.Counter
对象,其中计数在添加之前用浮点因子加权(参见代码)。每个计数器对象有大约5000个键,大多数(但不是全部)键在所有计数器对象之间共享。
我目前的代码非常慢。我通过使用以下函数按顺序合并它们来合并大约4000个计数器对象:
def addCounters(c1, c2, factor = 1.0):
merged = c1
for word, count in c2.iteritems():
if word in merged:
merged[word] = float(merged[word]) + float(count) * factor
else:
merged[word] = float(count) * factor
return merged
根据我的cProfile
测量值,4181个呼叫将以不可接受的缓慢25秒运行。这非常烦人,因为它也冻结了我的GUI。
ncalls tottime percall cumtime percall filename:lineno(function)
4181 1.056 0.000 25.088 0.006 MyClass.py:30(addCounters)
有没有人知道更快的方法呢?
答案 0 :(得分:3)
可能有所帮助的一些事情:
float
int
,请不要经常打开float
来电;只要您保证factor
为float
Counter
个对象未使用Counter
个特定功能,则转换为dict
或defaultdict(int)
(或defaultdict(float)
此功能)可能有所帮助; dict
和defaultdict
(在CPython引用解释器中)在C中实现,其中Counter
在Python中实现,这增加了所有用途的开销。在本地测试中(在Python 3.5上,确切地说,它可能无法精确匹配2.7性能),使用原始代码,除了使用defaultdict(int)
输入和输出之外什么都不做,减少了运行时间从~19秒到〜的相似输入集〜 12秒。x[k] += y * z
而不是x[k] = x[k] + y * z
避免两个单独的显式查找可能(温和地)提高速度(由于+=
的实施方式,实际发生了两次查找,但是字节代码稍微有点效率)word
中每个merged
是否存在,因为Counter
通过返回0
来处理此问题,无论密钥是否存在(并defaultdict(int)
1}}或defaultdict(float)
会为你创建它)忽略类型从Counter
更改为输入的其他内容,改进后的代码如下所示:
def addCounters(c1, c2, factor=1.0):
# Assume inputs are defaultdict(int), not Counter
merged = c1
factor = float(factor) # In case someone passes non-float default
for word, count in c2.iteritems():
merged[word] += count * factor
return merged
在Python 3.5的本地测试中(只有代码差异使用.items()
而不是.iteritems()
),在使用defaultdict(int)
和进行这些代码更改之间,在类似于您的数据集中描述,总时间从~19秒下降到~5.5秒。可能仍然太长而无法在GUI事件循环中被接受,但是在这方面很难改进。
答案 1 :(得分:1)
我在移动设备上,所以我无法进行配置,但下面的实现似乎有比原版更简化的反汇编字节代码。如果一个简单的词典没问题,可能值得一试。
def addCounters(c1, c2, factor=1.0):
return dict(c1, **{w:(c1[w] + c*factor) for w,c in c2.iteritems()})
如果您还需要支持普通dict
参数(而不仅仅是Counter
,其隐含行为类似defaultdict
,以便c1[w]
始终安全),您可以执行c1.get(w, 0)
。
def addCounters(c1, c2, factor=1.0):
return dict(c1, **{w:(c1.get(w, 0) + c*factor) for w,c in c2.iteritems()})
答案 2 :(得分:0)
部分灵感来自F先生,我想出了一些东西,在我的本地计算机上运行了4.3秒。如果有人能让它更快,我仍然会很高兴。我的方法适用于#pragma ms_struct reset
和dict
基本上,我将密钥“导出”成集,然后将它们分成3组:
collections.Counter
c1
两个键都出现了。
c2
和c1
中,可以轻松计算其计数def addCounters(c1,c2,factor = 1.0):
c2
我认为它运行得更快的主要原因是,当添加多个计数器/ dicts时,将始终将先前的累积量反馈给方法#make sure factor is float
factor = float(factor)
#get keys as sets
c1keys, c2keys = set(c1.keys()), set(c2.keys())
#keys only in c1
c1only = c1keys.difference(c2keys)
#keys only in c2
c2only = c1keys.difference(c1keys)
#keys guaranteed in both
both = c1keys.intersection(c2keys)
#generate dicts for keys which are unique to c1 or c2
c1onlydict = dict((w,c1[w]) for w in c1only)
c2onlydict = dict((w,c2[w]*factor) for w in c2only)
#merge
loners = dict(c1onlydict, **c2onlydict)
#now create the directory with keys which exist in both
bothdict = dict((key, c1[key] + c2[key]*factor) for key in both)
#merge everything
return dict(loners, **bothdict)
。因此,c1
(最容易创建并且可能具有高度优化的字节码)将变得非常大,而c1onlydict
和c2onlydict
相比之下会很小。