2010-12-24 18 views
123

Ich habe zwei Wörterbücher, aber zur Vereinfachung werde ich nehmen diese beiden:Vergleicht man zwei Wörterbücher in Python

>>> x = dict(a=1, b=2) 
>>> y = dict(a=2, b=2) 

Jetzt möchte ich, ob jedes key, value Paar in x vergleichen denselben entsprechenden Wert in y hat. Also schrieb ich diese:

>>> for x_values, y_values in zip(x.iteritems(), y.iteritems()): 
     if x_values == y_values: 
      print 'Ok', x_values, y_values 
     else: 
      print 'Not', x_values, y_values 

Und es funktioniert, da ein tuple zurückgegeben und dann im Vergleich zur Gleichheit.

Meine Fragen:

Ist das korrekt? Gibt es einen besseren Weg, dies zu tun? Besser nicht in Geschwindigkeit, ich spreche von Code Eleganz.

UPDATE: Ich habe vergessen zu erwähnen, dass ich überprüfen muss, wie viele key, value Paare sind gleich.

+7

'dh nennen haben' x == y'' shoul d ist wahr nach http://stackoverflow.com/a/5635309/186202 – Natim

Antwort

101

Wenn Sie, wie viele Werte übereinstimmen wissen wollen, in sowohl die Wörterbücher, sollten Sie gesagt haben, dass :)

Vielleicht so etwas wie folgt aus:

shared_items = set(x.items()) & set(y.items()) 
print len(shared_items) 
+32

Leider funktioniert das nicht, wenn die Werte im dict veränderbar sind (dh nicht hashbar). (Ex '{'a': {'b': 1}}' gibt 'TypeError: nicht hashbarer Typ: 'dict') –

+1

Derselbe Fehler, wenn ein Listenelement für den dict-Schlüssel vorhanden ist. Ich denke CMP ist der bessere Weg, es zu tun, wenn ich nichts vermisse. – Mutant

+0

@ Mutant, das ist ein anderes Problem. Sie können kein Wörterbuch mit einem 'list'-Schlüssel an erster Stelle erstellen. 'x = {[1,2]: 2}' wird fehlschlagen. Die Frage hat bereits gültige "dicts". – Annan

123

Was Sie tun möchten, ist einfach x==y

Was Sie tun, ist keine gute Idee, da die Elemente in einem Wörterbuch sind keine Ordnung haben soll. Sie könnten [('a',1),('b',1)] mit [('b',1), ('a',1)] vergleichen (gleiche Wörterbücher, andere Reihenfolge).

Zum Beispiel sehen:

>>> x = dict(a=2, b=2,c=3, d=4) 
>>> x 
{'a': 2, 'c': 3, 'b': 2, 'd': 4} 
>>> y = dict(b=2,c=3, d=4) 
>>> y 
{'c': 3, 'b': 2, 'd': 4} 
>>> zip(x.iteritems(), y.iteritems()) 
[(('a', 2), ('c', 3)), (('c', 3), ('b', 2)), (('b', 2), ('d', 4))] 

Der Unterschied ist nur ein Element, aber Ihr Algorithmus werden sehen, dass alle Artikel unterschiedlich sind

+0

@ THC4k, Entschuldigung für die Nennung nicht. Aber ich muss überprüfen, wie viele Werte in beiden Wörterbüchern übereinstimmen. – user225312

+0

Ok, also basierend auf meinem Update, ist meine Vorgehensweise noch inkorrekt? – user225312

+0

@A A: Ich habe hinzugefügt, warum Ihr Gerät nicht funktioniert, wenn Sie zählen möchten. –

45

Ich bin neu in Python, aber ich endete etwas zu tun, ähnlich wie @mouad

unmatched_item = set(dict_1.items())^set(dict_2.items()) 
len(unmatched_item) # should be 0 

Der XOR-Operator (^) sollten alle Elemente des dict beseitigen, wenn sie das gleiche in beiden dicts sind.

+1

Das ist genau was Ich suchte. =) Ich konnte nicht glauben, dass es keine einfache Lösung gibt. Sogar PHP hat eine Funktion dafür. Vielen Dank. – vellotis

+17

Leider funktioniert das nicht, wenn die Werte im Diktat änderbar (dh nicht hashbar) sind. (Ex '{'a': {'b': 1}}' 'gibt' TypeError: nicht hashbarer Typ: 'dict'') –

104
def dict_compare(d1, d2): 
    d1_keys = set(d1.keys()) 
    d2_keys = set(d2.keys()) 
    intersect_keys = d1_keys.intersection(d2_keys) 
    added = d1_keys - d2_keys 
    removed = d2_keys - d1_keys 
    modified = {o : (d1[o], d2[o]) for o in intersect_keys if d1[o] != d2[o]} 
    same = set(o for o in intersect_keys if d1[o] == d2[o]) 
    return added, removed, modified, same 

x = dict(a=1, b=2) 
y = dict(a=2, b=2) 
added, removed, modified, same = dict_compare(x, y) 
+4

Dieser behandelt tatsächlich änderbare Werte im dict! –

+4

Dies ist eine fantastische, elegante Antwort. IMO sollte dies akzeptiert werden. – Wikis

+0

Wenn ich dies ausführe, bekomme ich immer noch einen Fehler beim Umgang mit den veränderbaren Werten: ValueError: Der Wahrheitswert eines DataFrames ist mehrdeutig. Verwenden Sie a.empty, a.bool(), a.item(), a.any() oder a.all(). – Afflatus

6

@mouads Antwort ist nett, wenn Sie davon ausgehen, dass beide Wörterbücher nur einfache Werte enthalten. Wenn Sie jedoch Wörterbücher mit Wörterbüchern haben, erhalten Sie eine Ausnahme, da Wörterbücher nicht hashbar sind.

Aus der Spitze von meinem Kopf, so etwas wie dies funktionieren könnte:

def compare_dictionaries(dict1, dict2): 
    if dict1 == None or dict2 == None: 
     return False 

    if type(dict1) is not dict or type(dict2) is not dict: 
     return False 

    shared_keys = set(dict2.keys()) & set(dict2.keys()) 

    if not (len(shared_keys) == len(dict1.keys()) and len(shared_keys) == len(dict2.keys())): 
     return False 


    dicts_are_equal = True 
    for key in dict1.keys(): 
     if type(dict1[key]) is dict: 
      dicts_are_equal = dicts_are_equal and compare_dictionaries(dict1[key],dict2[key]) 
     else: 
      dicts_are_equal = dicts_are_equal and (dict1[key] == dict2[key]) 

    return dicts_are_equal 

Bitte beachte, dass ich über die Linie noch nicht glücklich bin:

dicts_are_equal = dicts_are_equal and (dict1[key] == dict2[key]) 

als die Werte dieser könnte sein Objekte. Es wäre schön zu überprüfen, ob die beiden Objekte eine vergleichbare Schnittstelle implementieren.

0
>>> hash_1 
{'a': 'foo', 'b': 'bar'} 
>>> hash_2 
{'a': 'foo', 'b': 'bar'} 
>>> set_1 = set (hash_1.iteritems()) 
>>> set_1 
set([('a', 'foo'), ('b', 'bar')]) 
>>> set_2 = set (hash_2.iteritems()) 
>>> set_2 
set([('a', 'foo'), ('b', 'bar')]) 
>>> len (set_1.difference(set_2)) 
0 
>>> if (len(set_1.difference(set_2)) | len(set_2.difference(set_1))) == False: 
... print "The two hashes match." 
... 
The two hashes match. 
>>> hash_2['c'] = 'baz' 
>>> hash_2 
{'a': 'foo', 'c': 'baz', 'b': 'bar'} 
>>> if (len(set_1.difference(set_2)) | len(set_2.difference(set_1))) == False: 
...  print "The two hashes match." 
... 
>>> 
>>> hash_2.pop('c') 
'baz' 

Hier ist eine andere Option:

>>> id(hash_1) 
140640738806240 
>>> id(hash_2) 
140640738994848 

So wie Sie die beiden IDs sind unterschiedlich sehen. Aber die rich comparison operators scheinen den Trick zu tun:

>>> hash_1 == hash_2 
True 
>>> 
>>> hash_2 
{'a': 'foo', 'b': 'bar'} 
>>> set_2 = set (hash_2.iteritems()) 
>>> if (len(set_1.difference(set_2)) | len(set_2.difference(set_1))) == False: 
...  print "The two hashes match." 
... 
The two hashes match. 
>>> 
4

Noch eine weitere Möglichkeit, bis zur letzten Note der OP, die Hashes zu vergleichen ist (SHA oder MD) die dicts als JSON abgeladen. Die Weg-Hashes sind so konstruiert, dass sie, wenn sie gleich sind, gleichwertig sind. Das ist sehr schnell und mathematisch fundiert.

import json 
import hashlib 

def hash_dict(d): 
    return hashlib.sha1(json.dumps(d, sort_keys=True)).hexdigest() 

x = dict(a=1, b=2) 
y = dict(a=2, b=2) 
z = dict(a=1, b=2) 

print(hash_dict(x) == hash_dict(y)) 
print(hash_dict(x) == hash_dict(z)) 
+0

Das ist völlig falsch, nur die Daten in json zu analysieren ist wirklich langsam. Dann ist Hashing, dass riesige Sring Sie gerade erstellt haben, noch schlimmer. Sie sollten das nie tun – Bruno

+5

@Bruno: Zitieren der OP: * "Besser nicht in Geschwindigkeit, ich spreche über Code Eleganz" * – WoJ

+1

Es ist überhaupt nicht elegant, fühlt sich unsicher und es ist zu kompliziert für ein wirklich einfaches Problem – Bruno

40

Verwenden Sie einfach:

assert cmp(dict1, dict2) == 0 
+9

Fehle ich etwas? ist diese Antwort nicht besser als die anderen? – Shoham

+4

Es scheint, dass die Aufgabe ist nicht nur zu überprüfen, ob der Inhalt der beiden gleich sind, sondern auch einen Bericht über die Unterschiede geben –

+16

Ich glaube, das ist identisch mit 'dict1 == dict2' –

-4
import json 

if json.dumps(dict1) == json.dumps(dict2): 
    print("Equal") 
+0

Dies kann nicht tun, was genau angefordert wurde, und zieht die JSON-Standard-Lib ein, aber es funktioniert (da 'Json-Dumps' mit den Standardeinstellungen deterministisch ist). –

25

wenn zwei Wörterbücher den gleichen Inhalt zu überprüfen, verwenden Sie einfach:

dic1 == dic2 

Von python docs:

Zur Veranschaulichung die folgenden Beispiele alle Rück ein Wörterbuch gleich { "one": 1, "zwei": 2, "drei": 3}:

>>> a = dict(one=1, two=2, three=3) 
>>> b = {'one': 1, 'two': 2, 'three': 3} 
>>> c = dict(zip(['one', 'two', 'three'], [1, 2, 3])) 
>>> d = dict([('two', 2), ('one', 1), ('three', 3)]) 
>>> e = dict({'three': 3, 'one': 1, 'two': 2}) 
>>> a == b == c == d == e 
True 
+2

Ich bin nicht einverstanden mit @ ErkinAlpGüney. Könnten Sie einen Beweis liefern? –

+3

Erstellen Sie einfach 2 gleiche dics und testen Sie den Code. –

+3

Ich stimme nicht mit @ ErkinAlpGüney. Die offizielle Dokumentation zeigt, dass == Wörterbücher tatsächlich nach Wert und nicht nach Adresse vergleicht. https://docs.python.org/2/library/stdtypes.html#mapping-types-dict –

2

-Code

def equal(a, b): 
    type_a = type(a) 
    type_b = type(b) 

    if type_a != type_b: 
     return False 

    if isinstance(a, dict): 
     if len(a) != len(b): 
      return False 
     for key in a: 
      if key not in b: 
       return False 
      if not equal(a[key], b[key]): 
       return False 
     return True 

    elif isinstance(a, list): 
     if len(a) != len(b): 
      return False 
     while len(a): 
      x = a.pop() 
      index = indexof(x, b) 
      if index == -1: 
       return False 
      del b[index] 
     return True 

    else: 
     return a == b 

def indexof(x, a): 
    for i in range(len(a)): 
     if equal(x, a[i]): 
      return i 
    return -1 

-Test

>>> a = { 
    'number': 1, 
    'list': ['one', 'two'] 
} 
>>> b = { 
    'list': ['two', 'one'], 
    'number': 1 
} 
>>> equal(a, b) 
True 
0

In PyUnit gibt es eine Methode, die Wörterbücher schön vergleicht. Ich habe es mit den folgenden zwei Wörterbüchern getestet und es macht genau das, wonach Sie suchen.

d1 = {1: "value1", 
     2: [{"subKey1":"subValue1", 
      "subKey2":"subValue2"}]} 
d2 = {1: "value1", 
     2: [{"subKey2":"subValue2", 
      "subKey1": "subValue1"}] 
     } 


def assertDictEqual(self, d1, d2, msg=None): 
     self.assertIsInstance(d1, dict, 'First argument is not a dictionary') 
     self.assertIsInstance(d2, dict, 'Second argument is not a dictionary') 

     if d1 != d2: 
      standardMsg = '%s != %s' % (safe_repr(d1, True), safe_repr(d2, True)) 
      diff = ('\n' + '\n'.join(difflib.ndiff(
          pprint.pformat(d1).splitlines(), 
          pprint.pformat(d2).splitlines()))) 
      standardMsg = self._truncateMessage(standardMsg, diff) 
      self.fail(self._formatMessage(msg, standardMsg)) 

Ich empfehle nicht, unittest in Ihren Produktionscode zu importieren. Mein Gedanke ist die Quelle in PyUnit könnte neu programmiert werden, um in der Produktion zu laufen. Es verwendet pprint, die die Wörterbücher "hübsch druckt". Es scheint ziemlich einfach zu sein, diesen Code so anzupassen, dass er "produktionsfertig" ist.

0

In Python 3.6, kann es als getan werden: -

if (len(dict_1)==len(dict_2): 
    for i in dict_1.items(): 
     ret=bool(i in dict_2.items()) 

ret Variable wahr ist, wenn alle Elemente von dict_1 in Gegenwart dict_2 in

0

Wörterbuch Ansicht Objekte sehen: https://docs.python.org/2/library/stdtypes.html#dict

Auf diese Weise können Sie dictView2 von dictView1 subtrahieren und es wird eine Reihe von Schlüssel/Wert-Paaren zurückgegeben, die sich in dictView2 unterscheiden:

original = {'one':1,'two':2,'ACTION':'ADD'} 
originalView=original.viewitems() 
updatedDict = {'one':1,'two':2,'ACTION':'REPLACE'} 
updatedDictView=updatedDict.viewitems() 
delta=original | updatedDict 
print delta 
>>set([('ACTION', 'REPLACE')]) 

Sie können diese Wörterbuchansichtsobjekte schneiden, Vereinigung, Unterschied (siehe oben), symmetrische Differenz.
Besser? Schneller?- nicht sicher, aber ein Teil der Standardbibliothek - die es ein großes Plus für die Portabilität

2

Um zu testen, ob zwei dicts sind gleich in Schlüsseln und Werten macht:

def dicts_equal(d1,d2): 
    """ return True if all keys and values are the same """ 
    return all(k in d2 and d1[k] == d2[k] 
       for k in d1) \ 
     and all(k in d1 and d1[k] == d2[k] 
       for k in d2) 

Wenn Sie die Werte zurückgeben möchten die unterscheiden, schreiben Sie es anders:

def dict1_minus_d2(d1, d2): 
    """ return the subset of d1 where the keys don't exist in d2 or 
     the values in d2 are different, as a dict """ 
    return {k,v for k,v in d1.items() if k in d2 and v == d2[k]} 

Sie würden es zweimal

dict1_minus_d2(d1,d2).extend(dict1_minus_d2(d2,d1)) 
Verwandte Themen