2012-08-10 38 views
12

我有一本字典d1和列表l1Python字典的理解很慢

字典鍵是字符串,值是我定義自己的對象。如果有幫助,我可以描述對象更詳細,但就目前而言,對象有一個列表屬性names,以及一些name元素可能會或可能不會出現在l1

我想做的是丟掉字典d1中的任何元素,其中該元素中的對象的name屬性不包含任何出現在l1中的元素。

作爲一個簡單的例子:

l1 = ['cat', 'dog', 'mouse', 'horse', 'elephant', 
     'zebra', 'lion', 'snake', 'fly'] 

d1 = {'1':['dog', 'mouse', 'horse','orange', 'lemon'], 
     '2':['apple', 'pear','cat', 'mouse', 'horse'], 
     '3':['kiwi', 'lime','cat', 'dog', 'mouse'], 
     '4':['carrot','potato','cat', 'dog', 'horse'], 
     '5':['chair', 'table', 'knife']} 

因此所得到的字典將或多或少相同,但每個列表中的元素將是從1鍵值對到4不包括水果和蔬菜,並且將不包含5鍵值面值爲無傢俱值出現在l1

爲此,我使用了嵌套列表/字典理解這是這樣的:

d2 = {k: [a for a in l1 if a in d1[k]] for k in d1.keys()} 
print(d2) 

>>>>{'1': ['dog', 'mouse', 'horse'], 
    '3': ['cat', 'dog', 'mouse'], 
    '2': ['cat', 'mouse', 'horse'], 
    '5': [], 
    '4': ['cat', 'dog', 'horse']} 

d2 = {k: v for k,v in d2.iteritems() if len(v)>0} 
print(d2) 

>>>>{'1': ['dog', 'mouse', 'horse'], 
    '3': ['cat', 'dog', 'mouse'], 
    '2': ['cat', 'mouse', 'horse'], 
    '4': ['cat', 'dog', 'horse'],} 

這似乎是工作,但對於大辭典,7000名+的項目,它需要大約20秒的工作,通過。本身並不可怕,但我需要在循環內進行10,000次迭代,因此目前不可行。有關如何快速做到這一點的任何建議?

+1

注意給大家:他是用Python 2.7版不是3,由於使用'itertitems',不要讓打印' ()'騙你 – jamylak 2012-08-10 14:08:16

+0

python 2.7有詞典理解嗎? – Claudiu 2012-08-10 14:12:47

+0

@Claudiu是的,他們是backported – jamylak 2012-08-10 14:13:49

回答

13

您有效地計算每個列表發生的文化在字典中的值的交集與列表l1。由於涉及到線性搜索,使用列表設置交叉點效率相當低。你應該把l1成一組,並使用set.intersection()或集合成員資格測試,而不是(這取決於它是否是可以接受的結果是一組再次)。

完整的代碼看起來是這樣的:

l1 = set(l1) 
d2 = {k: [s for s in v if s in l1] for k, v in d1.iteritems()} 
d2 = {k: v for k, v in d2.iteritems() if v} 

代替兩個字典推導的,它也可能是最好使用單一for循環這裏:

l1 = set(l1) 
d2 = {} 
for k, v in d1.iteritems(): 
    v = [s for s in v if s in l1] 
    if v: 
     d2[k] = v 
+0

爲了提高效率,我會把你的第一個代碼改成>>> >>> d2 =((k,[s代表v中的s,如果s代入l1])代替k,v代替d1.iteritems()) >>> d2 = {k:v for k,v in d2 if v}'。 – jamylak 2012-08-10 14:33:41

+0

@jamylak:你認爲這會比'for'循環更快嗎?我認爲這至少是非常醜陋的。 :) – 2012-08-10 14:36:22

+0

那麼它會比現在的代碼更有效率,它將再次通過d2運行。不確定第二個,將不得不''timeit' – jamylak 2012-08-10 14:39:43

4

問題不字典的理解,但嵌套的列表理解。您每次都在迭代相同的密鑰。這種事情最好用集合來完成。

s1 = set(l1) 
d2 = {k: list(s1.intersection(v)) for k, v in d1.items()} 
+2

爲了更有效地使用'iteritems' – jamylak 2012-08-10 14:15:00

+1

如果允許將'd1'和'd2'中的值設爲集合,它也會更高效。 – 2012-08-10 14:23:43

0

使用set

>>> l1 = ['cat', 'dog', 'mouse', 'horse', 'elephant', 
     'zebra', 'lion', 'snake', 'fly'] 
>>> d1 = {'1':['dog', 'mouse', 'horse','orange', 'lemon'], 
     '2':['apple', 'pear','cat', 'mouse', 'horse'], 
     '3':['kiwi', 'lime','cat', 'dog', 'mouse'], 
     '4':['carrot','potato','cat', 'dog', 'horse'], 
     '5':['chair', 'table', 'knife']} 
>>> l1_set = set(l1) 
>>> d2 = dict((k, set(d1[k]) & l1_set) for k in d1.keys()) 
>>> d2 
{'1': set(['horse', 'mouse', 'dog']), '3': set(['mouse', 'dog', 'cat']), '2': set(['horse', 'mouse', 'cat']), '5': set([]), '4': set(['horse', 'dog', 'cat'])} 
>>> d2 = dict((k, v) for k,v in d2.iteritems() if v) 
>>> d2 
{'1': set(['horse', 'mouse', 'dog']), '3': set(['mouse', 'dog', 'cat']), '2': set(['horse', 'mouse', 'cat']), '4': set(['horse', 'dog', 'cat'])} 
0

如果轉換l1set和稍微修改一下字典理解,你可以得到更快的這方面的工作大致三次:

l1 = set(['cat', 'dog', 'mouse', 'horse', 'elephant', 
     'zebra', 'lion', 'snake', 'fly']) 

d1 = {'1':['dog', 'mouse', 'horse','orange', 'lemon'], 
     '2':['apple', 'pear','cat', 'mouse', 'horse'], 
     '3':['kiwi', 'lime','cat', 'dog', 'mouse'], 
     '4':['carrot','potato','cat', 'dog', 'horse'], 
     '5':['chair', 'table', 'knife']} 

d2 = {k: [a for a in d1[k] if a in l1] for k in d1.keys()} 
print(d2) 

這裏是如何你可以基準性能:

import timeit 

t = timeit.Timer(
    "d2 = {k: [a for a in l1 if a in d1[k]] for k in d1.keys()}", 
    "from __main__ import (d1, l1)", 
    ) 
print "%.2f usec/pass" % (1000000 * t.timeit(number=100000)/100000) 

t = timeit.Timer(
    'd2 = {k: [a for a in d1[k] if a in l1] for k in d1.keys()}', 
    "from __main__ import (d1, l1)", 
    ) 
print "%.2f usec/pass" % (1000000 * t.timeit(number=100000)/100000) 

我在這裏假設您無法控制d1,並且將所有d1的值轉換爲過濾之前的集合太慢。

1
l1 = ['cat', 'dog', 'mouse', 'horse', 'elephant', 
     'zebra', 'lion', 'snake', 'fly'] 

d1 = {'1':['dog', 'mouse', 'horse','orange', 'lemon'], 
     '2':['apple', 'pear','cat', 'mouse', 'horse'], 
     '3':['kiwi', 'lime','cat', 'dog', 'mouse'], 
     '4':['carrot','potato','cat', 'dog', 'horse'], 
     '5':['chair', 'table', 'knife']} 

def gen_items(valid_name_set, d): 
    for k, v in d.iteritems(): 
     intersection = valid_name_set.intersection(v) 
     if intersection: # not empty 
      yield (k, intersection) 

print dict(gen_items(set(l1), d1)) 

輸出:

{'1': set(['dog', 'horse', 'mouse']), 
'2': set(['cat', 'horse', 'mouse']), 
'3': set(['cat', 'dog', 'mouse']), 
'4': set(['cat', 'dog', 'horse'])} 

或者:

from itertools import ifilter 
from operator import itemgetter 
set_l1 = set(l1) 
d2 = dict(ifilter(itemgetter(1), 
        ((k, set_l1.intersection(v)) for k, v in d1.iteritems())))