Zwei Wörterbücher in Python überschneiden

73

Ich arbeite an einem Suchprogramm über einen invertierten Index. Der Index selbst ist ein Wörterbuch, dessen Schlüssel Begriffe und dessen Werte selbst Wörterbücher für kurze Dokumente sind, mit ID-Nummern als Schlüssel und deren Textinhalt als Werte.

Um eine UND-Suche nach zwei Begriffen durchzuführen, muss ich daher deren Posting-Listen (Wörterbücher) schneiden. Was ist ein klarer (nicht unbedingt übermäßig kluger) Weg, dies in Python zu tun? Ich begann damit, es lange zu versuchen mit iter:

p1 = index[term1]  
p2 = index[term2]
i1 = iter(p1)
i2 = iter(p2)
while ...  # not sure of the 'iter != end 'syntax in this case
...
Nicole
quelle
{i: diktiere (p1 [i], ** p2 [i]) für i in p1, wenn i in p2}
mtadd
Mein obiger Kommentar wird Ihre {term:{doc_id:p1[term][doc_id] for doc_id in p1[term] if doc_id in p2[term]} for term in p1 if term in p2}
Termwörterbücher
2
Es ist schrecklich unklar, was Ihre gewünschte Ausgabe ist (oder sogar was Ihre Eingabe ist). Nach Ihrer Beschreibung des Problems zu urteilen, klingt es so, als hätten Sie verschachtelte Wörterbücher, die Sie ... "überschneiden" möchten, was auch immer das bedeutet. Die Antwort, die Sie akzeptiert haben, schneidet jedoch nicht nur keine verschachtelten Wörterbücher, sondern auch keine Wörterbücher. Es schneidet lediglich die Tasten zweier Wörterbücher. Bitte klären Sie Ihre Frage und aktualisieren Sie sie mit einem minimal reproduzierbaren Beispiel .
Aran-Fey
Mögliches Duplikat der effizienten Kreuzung
Aran-Fey

Antworten:

87

In Python verwenden Sie den &Operator , um den Schnittpunkt von Mengen zu berechnen, und Wörterbuchschlüssel sind satzähnliche Objekte (in Python 3):

dict_a = {"a": 1, "b": 2}
dict_b = {"a": 2, "c": 3} 

intersection = dict_a.keys() & dict_b.keys()  # {'a'}

In Python 2 müssen Sie die Wörterbuchschlüssel in Sets selbst konvertieren:

keys_a = set(dict_a.keys())
keys_b = set(dict_b.keys())
intersection = keys_a & keys_b
James
quelle
114

Eine wenig bekannte Tatsache ist, dass Sie dazu keine sets konstruieren müssen:

In Python 2:

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

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

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

In Python 3 ersetzen Sie viewkeysdurch keys; Gleiches gilt für viewvaluesund viewitems.

Aus der Dokumentation von 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

Für größere dicts ist dies auch etwas schneller als sets zu konstruieren und sie dann zu schneiden:

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

Wir vergleichen hier Nanosekunden, die für Sie möglicherweise von Bedeutung sind oder nicht. In jedem Fall erhalten Sie ein zurück set, so dass die Verwendung von viewkeys/ keysein bisschen Unordnung beseitigt.

Phillip Cloud
quelle
4
viewkeys()ist "Neu in Version 2.7"
Bis auf weiteres angehalten.
1
Irgendwie ist dies etwas langsamer (~ 12%) langsamer als die set(d1.keys()) & set(d2.keys())Methode. Ich verstehe jedoch nicht, warum das so ist.
Dan
@ Dan: auch wenn es (wahrscheinlich) langsamer sieht es mehr Pythonic mir
Azat Ibrakov
Mit dem future ( pip install future) -Paket funktioniert Folgendes in Python 2 und 3:from future.utils import viewkeys; viewkeys(d1) & viewkeys(d2)
teichert
78
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}
emnoor
quelle
16
Dies sollte die Antwort sein, da dies die einzige ist, die auf einfache Weise zeigt, wie man ein Kreuzungsdiktat und keine Schlüssellisten erhält.
Rafe
20

In Python 3 können Sie verwenden

intersection = dict(dict1.items() & dict2.items())
union = dict(dict1.items() | dict2.items())
difference = dict(dict1.items() ^ dict2.items())
dccsillag
quelle
2
Für dict1 = {1: 1, 2:2}und dict2 = {2:4, 3:3}das intersection == set(). Wahrscheinlich ist dies nicht das, was OP will.
WloHu
@ JCode Ich bin nicht sicher, ob ich es verstehe. intersection = dict(...)wandelt es wieder in a um dict. Außerdem habe ich es gerade mit dict1 = {1: 1, 2: 2}und dict2 = {2: 4, 3: 3}(den obigen Wörterbüchern) getestet und intersection == set()ist False. Es ist ein leeres Wörterbuch.
dccsillag
@DCPY Sorry, das habe ich verpasst dict(...). Der Punkt ist, dass in meinem Beispiel das Ergebnis leer ist. Wenn man bedenkt, welche OP-akzeptierten und häufigeren Anwendungsfälle sich überschneiden, .items()ist dies nur dann sinnvoll, wenn Sie nach wörtlichen Duplikaten suchen.
WloHu
@JCode In diesem Fall kann davon ausgegangen werden, dass alle Werte eindeutig sind. Dann anstelle von dict1verwenden {v: k for k, v in dict1.items()}und statt dict2verwenden {v: k for k, v in dict2.items()}. Wenn Sie den Fall möchten, in dem es sich entweder um den Schlüssel oder das Wörterbuch handelt, verwenden Sie die Vereinigung der in der Antwort angegebenen Schnittmenge und der in diesem Kommentar angegebenen Schnittmenge.
dccsillag
2

Schließen Sie die Wörterbuchinstanzen einfach mit einer einfachen Klasse ein, die beide gewünschten Werte erhält

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']
Eric Urban
quelle
11
Warum sollte ich den ganzen Code schreiben wollen? Wenn ich das tun würde, würde ich nicht in Python codieren, sondern in Java! :)
Robert Moskal
2

Okay, hier ist eine verallgemeinerte Version des obigen Codes in Python3. Es ist optimiert, um Verständnisse und satzartige Diktatansichten zu verwenden, die schnell genug sind.

Die Funktion schneidet beliebig viele Dikte und gibt ein Dikt mit gemeinsamen Schlüsseln und einer Reihe gemeinsamer Werte für jeden gemeinsamen Schlüssel zurück:

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

Anwendungsbeispiel:

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

Hier müssen die diktierten Werte hashbar sein. Wenn dies nicht der Fall ist, können Sie einfach die Klammern {} in list [] ändern:

result = {key:[d[key] for d in dicts] for key in comm_keys}
thodnev
quelle
Ich übergebe eine Liste von Diktaten an die Funktion, aber es gibt einen Fehler. Wie kann ich die obige Funktion so bearbeiten, dass eine Liste von Diktaten übergeben wird und ein Schlüssel-Wert-Paar mit gemeinsamen Schlüsseln sowie ein Wert erhalten wird?
Lernprogrammierung
@learnningprogramming, hoffe, Sie haben bereits herausgefunden, wie Sie das Problem lösen können, aber für andere Neugierige: *dictsAls Funktionsargumente müssen Sie zahlreiche Argumente übergeben, keine Liste davon. Wenn Sie die Liste lst = [dict1, dict2, dict3, ...]entweder verwenden dict_intersect(dict1, dict2, dict3, ...)oder entpacken müssendict_intersect(*lst)
bis
0

Ihre Frage ist nicht präzise genug, um eine einzige Antwort zu geben.

1. Schlüsselkreuzung

Wenn Sie IDs von Posts ( Credits an James ) überschneiden möchten, gehen Sie wie folgt vor :

common_ids = p1.keys() & p2.keys()

Wenn Sie jedoch Dokumente iterieren möchten, müssen Sie berücksichtigen, welcher Beitrag Priorität hat. Ich gehe davon aus, dass dies der Fall ist p1. Das Iterieren von Dokumenten für common_idsist collections.ChainMapam nützlichsten:

from collections import ChainMap
intersection = {id: document
                for id, document in ChainMap(p1, p2)
                if id in common_ids}
for id, document in intersection:
    ...

Oder wenn Sie kein separates intersectionWörterbuch erstellen möchten :

from collections import ChainMap
posts = ChainMap(p1, p2)
for id in common_ids:
    document = posts[id]

2. Elemente Schnittpunkt

Wenn Sie Elemente beider Beiträge überschneiden möchten , dhID s und Dokumente abgleichen möchten , verwenden Sie den folgenden Code ( Gutschriften für DCPY ). Dies ist jedoch nur dann nützlich, wenn Sie nach Duplikaten suchen.

duplicates = dict(p1.items() & p2.items())
for id, document in duplicates:
    ...

3. Iterieren Sie über p1'UND' p2.

Wenn Sie mit " UND" suchen und beide Beiträge iterdurchsuchen möchten, ist es am besten, (fast) alle Elemente in mehreren Beiträgen zu durchlaufen:collections.ChainMap

from collections import ChainMap
for id, document in ChainMap(p1, p2):
    ...
WloHu
quelle
0
def two_keys(term_a, term_b, index):
    doc_ids = set(index[term_a].keys()) & set(index[term_b].keys())
    doc_store = index[term_a] # index[term_b] would work also
    return {doc_id: doc_store[doc_id] for doc_id in doc_ids}

def n_keys(terms, index):
    doc_ids = set.intersection(*[set(index[term].keys()) for term in terms])
    doc_store = index[term[0]]
    return {doc_id: doc_store[doc_id] for doc_id in doc_ids}

In [0]: index = {'a': {1: 'a b'}, 
                 'b': {1: 'a b'}}

In [1]: two_keys('a','b', index)
Out[1]: {1: 'a b'}

In [2]: n_keys(['a','b'], index)
Out[2]: {1: 'a b'}

Ich würde empfehlen, Ihren Index von zu ändern

index = {term: {doc_id: doc}}

auf zwei Indizes, einen für die Begriffe und dann einen separaten Index für die Werte

term_index = {term: set([doc_id])}
doc_store = {doc_id: doc}

Auf diese Weise speichern Sie nicht mehrere Kopien derselben Daten

Aaron Goldman
quelle