2012-09-19 128 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

相關問題