2013-09-01 18 views
42

Pracuję nad programem wyszukiwania nad odwróconym indeksem. Sam indeks jest słownikiem, którego kluczami są terminy i których wartości same w sobie są słownikami krótkich dokumentów, z numerami identyfikacyjnymi jako kluczami i ich treścią tekstową jako wartościami.Przecięcie dwóch słowników w języku Python

Aby wykonać wyszukiwanie "ORAZ" dla dwóch terminów, muszę przetrzeć ich listy wpisów (słowniki). Co to jest jasny (niekoniecznie przesadnie sprytny) sposób na zrobienie tego w Pythonie? Zacząłem próbując ją długa droga z iter:

p1 = index[term1] 
p2 = index[term2] 
i1 = iter(p1) 
i2 = iter(p2) 
while ... # not sure of the 'iter != end 'syntax in this case 
... 
+0

{i: dict (p1 [i], * * p2 [i]) dla i w p1, jeśli w p2} – mtadd

+0

Mój powyższy komentarz przetnie słowniki terminów, ale zrewolucjonizuje twoje listy wpisów ... jeśli chcesz również przeciąć listy wysyłkowe na swoich numerach identyfikacyjnych dokumentów , możesz użyć '{term: {doc_id: p1 [wyrażenie] [doc_id] dla id_ed w p1 [wyrażenie] jeśli doc_id w p2 [wyrażenie]} dla terminu w p1, jeśli termin w p2}' – mtadd

Odpowiedz

43

Można łatwo obliczyć przecięcie zbiorów, więc tworzenie zestawów z kluczy i używać ich do skrzyżowania:

keys_a = set(dict_a.keys()) 
keys_b = set(dict_b.keys()) 
intersection = keys_a & keys_b # '&' operator is used for set intersection 
2

Wystarczy owinąć te przypadki ze słownika prostego klasy, który dostaje obie wartości chcesz

class DictionaryIntersection(object): 
    def __init__(self,dictA,dictB): 
     self.dictA = dictA 
     self.dictB = dictB 

    def __getitem__(self,attr): 
     if attr not in self.dictA or attr not in self.dictB: 
      raise KeyError('Not in both dictionaries,key: %s' % attr) 

     return self.dictA[attr],self.dictB[attr] 

x = {'foo' : 5, 'bar' :6} 
y = {'bar' : 'meow' , 'qux' : 8} 

z = DictionaryIntersection(x,y) 

print z['bar'] 
+5

Dlaczego chciałbym pisać cały ten kod?Gdybym to zrobił, nie pisałbym w pythonie, ale java! :) –

79

mało znany jest fakt, że nie trzeba konstruować set y do tego celu:

W Pythonie 2:

In [78]: d1 = {'a': 1, 'b': 2} 

In [79]: d2 = {'b': 2, 'c': 3} 

In [80]: d1.viewkeys() & d2.viewkeys() 
Out[80]: {'b'} 

W Pythonie 3 zastąpić viewkeys z keys; to samo dotyczy viewvalues i .

Z dokumentacji viewitems:

In [113]: d1.viewitems?? 
Type:  builtin_function_or_method 
String Form:<built-in method viewitems of dict object at 0x64a61b0> 
Docstring: D.viewitems() -> a set-like object providing a view on D's items 

Dla większych dict s to także nieco szybciej niż konstruowanie set s, a następnie przecinających je:

In [122]: d1 = {i: rand() for i in range(10000)} 

In [123]: d2 = {i: rand() for i in range(10000)} 

In [124]: timeit d1.viewkeys() & d2.viewkeys() 
1000 loops, best of 3: 714 µs per loop 

In [125]: %%timeit 
s1 = set(d1) 
s2 = set(d2) 
res = s1 & s2 

1000 loops, best of 3: 805 µs per loop 

For smaller `dict`s `set` construction is faster: 

In [126]: d1 = {'a': 1, 'b': 2} 

In [127]: d2 = {'b': 2, 'c': 3} 

In [128]: timeit d1.viewkeys() & d2.viewkeys() 
1000000 loops, best of 3: 591 ns per loop 

In [129]: %%timeit 
s1 = set(d1) 
s2 = set(d2) 
res = s1 & s2 

1000000 loops, best of 3: 477 ns per loop 

Mamy tu porównanie nanosekund, co może lub może nie mieć dla ciebie znaczenia. W każdym razie, odzyskujesz set, więc używanie viewkeys/keys eliminuje trochę bałaganu.

+4

'viewkeys()' jest "Nowością w wersji 2.7" –

+0

W jakiś sposób kończy się to * czasami tak nieznacznie * (~ 12%) wolniej obliczyć niż 'set (d1.keys()) & set (d2 .keys()) 'metoda. Jednak nie rozumiem, dlaczego tak się stało. – Dan

+0

@Dan: nawet jeśli jest (prawdopodobnie) wolniej, wygląda bardziej * Pythonicznie * dla mnie –

43
In [1]: d1 = {'a':1, 'b':4, 'f':3} 

In [2]: d2 = {'a':1, 'b':4, 'd':2} 

In [3]: d = {x:d1[x] for x in d1 if x in d2} 

In [4]: d 
Out[4]: {'a': 1, 'b': 4} 
+8

To powinno być odpowiedzią, ponieważ jest to jedyna metoda, która w prosty sposób pokazuje, jak uzyskać dyktowanie skrzyżowania, a nie listy kluczy. – Rafe

1

Okay, tutaj jest uogólniona wersja kodu powyżej w Python3. Jest zoptymalizowany do korzystania ze zrozumiałych widoków, które są wystarczająco szybkie.

funkcja przecina arbitralnych wiele dicts i zwraca dict ze wspólnych kluczy oraz zbiór wspólnych wartości dla każdego wspólnego klucza: przykład

def dict_intersect(*dicts): 
    comm_keys = dicts[0].keys() 
    for d in dicts[1:]: 
     # intersect keys first 
     comm_keys &= d.keys() 
    # then build a result dict with nested comprehension 
    result = {key:{d[key] for d in dicts} for key in comm_keys} 
    return result 

Zastosowanie:

a = {1: 'ba', 2: 'boon', 3: 'spam', 4:'eggs'} 
b = {1: 'ham', 2:'baboon', 3: 'sausages'} 
c = {1: 'more eggs', 3: 'cabbage'} 

res = dict_intersect(a, b, c) 
# Here is res (the order of values may vary) : 
# {1: {'ham', 'more eggs', 'ba'}, 3: {'spam', 'sausages', 'cabbage'}} 

Tutaj wartości dict musi być hashable, jeśli nie, możesz po prostu zmienić nawiasy {} na listę []:

result = {key:[d[key] for d in dicts] for key in comm_keys} 
+0

Przekazuję listę dyktorów do funkcji, ale daje ona błąd. jak mogę edytować powyższą funkcję, aby alist of dict został przekazany, a klucz: para wartości ze zwykłymi kluczami oraz wartość została uzyskana? – learnningprogramming

+0

@plamowanie programowania, mam nadzieję, że już wiesz, jak rozwiązać problem, ale dla innych ciekawy: '* dyktuje' jako argumenty funkcji oznacza, że ​​musisz przekazać wiele argumentów, a nie ich listę. Jeśli masz 'lst = [dict1, dict2, dict3, ...]' użyj 'dict_intersect (dict1, dict2, dict3, ...)' lub rozpakuj listę 'dict_intersect (* lst)' – thodnev

Powiązane problemy