2012-09-19 95 views
3

给定一个包含数千万个键值对(字符串:整数)的大型字典(实际上,一个defaultdict)。从字典中删除大量密钥的最快方法

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

这样做的最快方法是什么?

+0

请问您是否清楚一点,确切的情况是什么?它是按键上的正则表达式,还是像键中的“foo”一样的条件,或者该键是16字节,还是... – kojiro

+0

@kojiro澄清。 – James

+0

如果你经常这样做,你确实需要最快的方法来做到这一点,你可能想要改变数据结构,或者甚至可以编写自己的类来优化这种情况。从快速检查中,使用Cocoa的字典和过滤方法(通过PyObjC)可以在不同情况下快8倍到快4倍。 (我并不是建议你使用NSDictionary,只是指出你可以得到多少差异。) – abarnert

回答

3

我觉得字典的基于迭代器的再生是一个好办法:

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 

在Linux上测量64位Python 2.7.3 2.6.32-34-generic o具有24 GB内存的Xeon E5630。峰值内存使用率低于10%(通过顶部进行监控)。

结论

  1. 方法1所述的性能和3是独立于垃圾收集的状态。
  2. 方法2由垃圾回收显着减慢。方法1和3总是更快,除了禁用垃圾回收的情况,没有项目被过滤掉。
  3. 如果大多数项目被期望过滤出来,使用方法1(字典理解)。如果你希望抛出多达一半的键(或者甚至更多,需要更好的基准测试),然后使用方法3.

我会去任何情况下的方法1,因为它是比方法3更干净的代码和性能差异不大。但这完全取决于你。

+0

随着Python 2.7,你可以使用一个字典理解 – kojiro

+0

当然,谢谢,@kojiro! –

+0

你能告诉我们:(1)你在什么操作系统,(2)是否您使用的是32位或64位Python,(3)每次测试的峰值内存使用量(您需要将其分成两个单独的脚本),以及(4)方法2是否触发您的系统页面出内存交换?因为从我看到的方法2明显更快,除了在64位Python它可以通过足够的临时内存运行导致交换,使事情一个数量级(Linux)或两个( Mac)相对较慢 – abarnert

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

这将根据您的病情新的字典,这在内存友好(由于iteritems和发电机)和有效的方式(不是一大堆的功能/方法调用)。

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,但他们应该很快。

+0

第二种解决方案比第一种解决方案更快,以防从字典中删除太多内容(如预期的那样,参考我的a nswer)。 –

2

参考:(我假设模== 0相比处于同一数量级上的< 20的速度,但是这不是真正相关的这些测试)

>>> 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

你检查了两个结果的平等吗? –

+0

你所有的值都是1;) – James

+0

@ Jan-PhilipGehrcke是的,但后来我意识到OP要求*值*并且改变了测试而没有使值变得有意义。只要这些非常长的测试完成,立即更新:) – kojiro

相关问题