从字典中删除大量密钥的最快方法

时间:2012-09-19 23:06:40

标签: python python-2.7

给定一个大字典,(实际上是一个defaultdict),有数千个键值对(字符串:整数)。

我希望根据值上的简单条件(例如value > 20)删除大约一半的键/值对。

最快的方法是什么?

4 个答案:

答案 0 :(得分:3)

我认为基于迭代器的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期间默认为switched off。它被关闭以减少测量中的噪声。但是,这可以完全改变方法的排名。我们来看看:

预先基准代码:

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. 方法1和3的性能与垃圾收集的状态无关。
  2. 垃圾收集显着减慢了方法2的速度。方法1和3总是更快,除了禁用垃圾收集的情况,没有项目被过滤掉。
  3. 如果预计会过滤掉大多数项目,请使用方法1(词典理解)。如果您希望将密钥数量减少一半(或者甚至更多,需要更精细的基准测试),那么请使用方法3.
  4. 在任何情况下我都会选择方法1,因为它比方法3更清晰,并且性能差异不大。但这完全取决于你。

答案 1 :(得分:2)

dict((k,v) for k,v in original_dict.iteritems() if condition)

这会根据您的条件创建一个新的字典,在一个内存友好的(由于iteritems和生成器)和有效的方式(不是很多函数/方法调用)。

答案 2 :(得分:2)

供参考:

>>> 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比较的速度与&lt; 20的顺序相同,但这与这些测试并不相关。)对于一个非常大的字典,它们大约相同的数量级,但我觉得就地速度要快一点。

答案 3 :(得分:1)

如果您可以制作新的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]

我不确定这些是快速 est ,但它们应该很快。