给定一个大字典,(实际上是 a defaultdict
)具有数千万个键值对(字符串:整数)。
我想根据值的简单条件(例如value > 20
)删除大约一半的键/值对。
最快的方法是什么?
给定一个大字典,(实际上是 a defaultdict
)具有数千万个键值对(字符串:整数)。
我想根据值的简单条件(例如value > 20
)删除大约一半的键/值对。
最快的方法是什么?
我认为基于迭代器的 dict 再生是一个好方法:
newdict = dict((k,v) for k,v in d.iteritems() if v > 20)
或者
newdict = {k: v for k,v in d.iteritems() if v > 20}
在 Python 2.7 中。
请注意,您必须小心使用d = {k: v for k,v in d.iteritems() if v > 20}
. 相反,您应该调用
d.clear()
d.update({k: v for k,v in d.iteritems() if v > 20})
这样,对数据的旧引用d
也将引用过滤后的数据。
编辑:
让我们通过基准比较这个线程中讨论的三种方法:
结果显然取决于要“删除”的字典的比例(这可能是不可预测的,但只有线程开启者知道)。它也可能高度依赖于垃圾收集的活动,默认情况下在timeit
. 它被关闭以减少测量中的噪音。但是,这可以完全改变方法的排名。我们来看一下:
预先基准代码:
from timeit import timeit
n = 2
N = "10**7"
mod = "9999999"
gc = "False"
print "N: %s; mod: %s; garbage collection: %s" % (N, mod, gc)
setup ="""
N = %s
mod = %s
d = {x:1 for x in xrange(N)}
if %s:
gc.enable()""" % (N, mod, gc)
t = timeit(
'd = {k:v for k, v in d.iteritems() if not k % mod}',
setup=setup,
number=n)
print "%s times method 1 (dict comp): %.3f s" % (n, t)
t = timeit(
'''
for k, v in d.items():
if k % mod:
del d[k]
''',
setup=setup,
number=n)
print "%s times method 2 (key deletion within for loop over d.items()): %.3f s" % (n, t)
t = timeit('''
removekeys = [k for k, v in d.iteritems() if k % mod]
for k in removekeys:
del d[k]
''',
setup=setup,
number=n)
print "%s times method 3 (key deletion after list comp): %.3f s" %(n, t)
案例1(没有过滤掉字典的项目):
启用垃圾收集:
N: 10**7; mod: 1; garbage collection: True
2 times method 1 (dict comp): 4.701
2 times method 2 (key deletion within for loop over d.items()): 15.782
2 times method 3 (key deletion after list comp): 2.024
垃圾收集已禁用:
N: 10**7; mod: 1; garbage collection: False
2 times method 1 (dict comp): 4.701
2 times method 2 (key deletion within for loop over d.items()): 4.268
2 times method 3 (key deletion after list comp): 2.027
案例2(字典的一半项目被过滤掉):
启用垃圾收集:
N: 10**7; mod: 2; garbage collection: True
2 times method 1 (dict comp): 3.449 s
2 times method 2 (key deletion within for loop over d.items()): 12.862 s
2 times method 3 (key deletion after list comp): 2.765 s
垃圾收集已禁用:
N: 10**7; mod: 2; garbage collection: False
2 times method 1 (dict comp): 3.395 s
2 times method 2 (key deletion within for loop over d.items()): 4.175 s
2 times method 3 (key deletion after list comp): 2.893 s
案例3(几乎所有字典的项目都被过滤掉了):
启用垃圾收集:
N: 10**7; mod: 9999999; garbage collection: True
2 times method 1 (dict comp): 1.217 s
2 times method 2 (key deletion within for loop over d.items()): 9.298 s
2 times method 3 (key deletion after list comp): 2.141 s
垃圾收集已禁用:
N: 10**7; mod: 9999999; garbage collection: False
2 times method 1 (dict comp): 1.213 s
2 times method 2 (key deletion within for loop over d.items()): 3.168 s
2 times method 3 (key deletion after list comp): 2.141 s
在具有 24 GB 内存的 Xeon E5630 上的 Linux 2.6.32-34-generic 上的 64 位 Python 2.7.3 上测量。峰值内存使用低于 10 %(通过顶部监控)。
结论
无论如何我都会选择方法 1,因为它的代码比方法 3 更简洁,而且性能差异不大。但这完全取决于你。
dict((k,v) for k,v in original_dict.iteritems() if condition)
这会根据您的条件以内存友好(由于iteritems
和生成器)和有效的方式(而不是大量的函数/方法调用)创建一个新字典。
以供参考:
>>> from timeit import timeit as t
# Create a new dict with a dict comprehension
>>> t('x={k:v for k, v in x.iteritems() if v % 2}', 'x={x:x for x in xrange(10**7)}', number=30)
100.02150511741638
# Delete the unneeded entries in-place
>>> t('''for k, v in x.items():
... if v % 2 != 0:
... del x[k]''', 'x={x:x for x in xrange(10**7)}', number=30)
89.83732604980469
(我假设模数 == 0 比较的速度与 < 20 的数量级相同,但这与这些测试并不真正相关。)对于非常大的 dict,它们的数量级大致相同,但我猜就地速度要快一些。
如果您可以制作新的dict
:
dict((k, v) for k,v in D.iteritems() if k != "foo")
如果你真的想修改原来的:
removekeys = [k for k, v in D.iteritems() if k == "foo"]
for k in removekeys: del D[k]
我不确定这些是最快的,但它们应该很快。