2010-12-24 4 views
123

У меня есть два словаря, но для упрощения, я буду считать эти два:Сравнение два словарей в Python

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

Теперь я хочу, чтобы сравнить, есть ли каждая key, value пары в x то же самое соответствующее значение в y. Так что я написал это:

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

И это работает так как tuple возвращается, а затем сравнить на равенство.

Мои вопросы:

Это правильно? Есть ли способ ? Лучше не в скорости, я говорю об элегантности кода.

ОБНОВЛЕНИЕ: Я забыл упомянуть, что я должен проверить, сколько пар key, value равны.

+7

'' х == y'' Шоул d быть правдой согласно http://stackoverflow.com/a/5635309/186202 – Natim

ответ

101

Если вы хотите знать, сколько значений матча в как словари, вы должны были сказать, что :)

Может быть что-то вроде этого:

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

К сожалению, это не работает, если значения в dict изменяются (т. Е. Не хешируются). (Ex '{'a': {'b': 1}}' дает 'TypeError: unhashable type: 'dict'') –

+1

Такая же ошибка, если есть элемент списка для ключа dict. Я думаю, что cmp - лучший способ сделать это, если я ничего не потеряю. – Mutant

+0

@Mutant это другая проблема. Вы не можете создать словарь с ключом «list». 'x = {[1,2]: 2}' не удастся. Вопрос уже имеет действительные 'dicts'. – Annan

123

То, что вы хотите сделать, это просто x==y

Что вы делаете, это не очень хорошая идея, потому что элементы в словаре не должны иметь какой-либо заказ. Вы можете сравнивать [('a',1),('b',1)] с [('b',1), ('a',1)] (те же словари, разный порядок).

Например, увидеть это:

>>> 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))] 

Разница только один пункт, но ваш алгоритм будет видеть, что всех элементов отличаются

+0

@ THC4k, извините за отсутствие упоминания. Но я должен проверить, сколько значений соответствует в обоих словарях. – user225312

+0

Итак, основываясь на моем обновлении, мой способ делать все еще неправильно? – user225312

+0

@A A: Я добавил, почему вы не работаете, когда хотите подсчитать. –

45

Я новичок в Python, но я в конечном итоге делаю что-то похожее на @mouad

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

Оператор XOR (^) следует исключить все элементы Словаря, когда они одинаковы в обоих dicts.

+1

Это как раз то, что Я искал. =) Я не мог поверить, что нет простого решения. Даже PHP имеет функцию для него. Благодарю. – vellotis

+17

К сожалению, это не работает, если значения в dict изменяются (т. Е. Не хешируются). (Ex '{'a': {'b': 1}}' дает 'TypeError: unhashable type: '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

Этот фактически обрабатывает изменяемые значения в dict! –

+4

Это фантастический, изящный ответ. ИМО это должно быть принято. – Wikis

+0

Когда я запустил это, я все равно получаю сообщение об ошибке с изменением значений: ValueError: Значение истины DataFrame неоднозначно. Используйте a.empty, a.bool(), a.item(), a.any() или a.all(). – Afflatus

6

Ответ @mouad хороший, если вы предполагаете, что оба словаря просто содержат простые значения. Однако, если у вас есть словари, содержащие словари, вы получите исключение, поскольку словари не хешируются.

Off верхней части моей головы, что-то подобное может работать:

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 

Обратите внимание, что я еще не рад линии:

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

как значения они могут быть объекты. Было бы неплохо иметь возможность проверить, реализуют ли оба объекта сопоставимый интерфейс.

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' 

Вот еще один вариант:

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

Итак, как вы видите два идентификаторы различны. Но rich comparison operators, кажется, сделать трюк:

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

Еще одна возможность, до последней ноты ОП, чтобы сравнить хэши (SHA или MD) из dicts сброшенных в формате JSON. Способ построения хэшей гарантирует, что, если они равны, исходные строки также равны. Это очень быстро и математически звучит.

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

Это совершенно неправильно, просто разбирать данные в json очень медленно. Тогда хеширование того, что только что созданная огромная песня еще хуже. Вы никогда не должны этого делать – Bruno

+5

@Bruno: цитируя OP: * «Лучше не в скорости, я говорю об элегантности кода» * – WoJ

+1

Это совсем не элегантно, он чувствует себя небезопасно, и это слишком сложно для действительно простой проблемы – Bruno

40

Просто используйте:

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

Я что-то упустил? разве этот ответ не лучше других? – Shoham

+4

Кажется, что задача состоит не только в том, чтобы проверить, совпадают ли содержимое обоих, но и дать отчет о различиях. –

+16

Я считаю, что это идентично 'dict1 == dict2' –

-4
import json 

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

Это может не делать то, что было запрошено точно, и тянет в json std lib, но он работает (поскольку 'json.dumps' детерминирован с настройками по умолчанию). –

25

Чтобы проверить, если два словаря имеют одинаковое содержание, просто используйте:

dic1 == dic2 

От python docs:

Для иллюстрации, следующие примеры все возвращает словарь, равный { "один": 1, "два": 2, "три": 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

Я не согласен с @ ErkinAlpGüney. Не могли бы вы предоставить доказательство? –

+3

Просто создайте 2 равных дика и проверьте код. –

+3

Я не согласен с @ ErkinAlpGüney. Официальная документация показывает, что == действительно сравнивает словари по значению, а не по адресу. https://docs.python.org/2/library/stdtypes.html#mapping-types-dict –

2

Код

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 

Тест

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

В PyUnit - это метод, который красиво сравнивает словари. Я тестировал его, используя следующие два словаря, и он делает именно то, что вы ищете.

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)) 

Я не рекомендую импортировать unittest в ваш производственный код. Моя мысль о том, что источник в PyUnit может быть переделан для запуска в производство. Он использует pprint, который «красиво печатает» словари. Кажется довольно легко адаптировать этот код к «готовому производству».

0

В Python 3.6, это может быть сделано, как: -

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

переменная RET будет справедливо, если все пункты dict_1 в настоящее время в dict_2

0

см словарю вид объектов: https://docs.python.org/2/library/stdtypes.html#dict

Таким образом вы можете вычесть dictView2 из dictView1, и он вернет набор пар ключ/значение, которые различаются в dictView2:

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')]) 

Вы можете пересечь, объединить, разницу (показано выше), симметричную разницу этих объектов просмотра словаря.
Лучше? Быстрее?- не уверен, но часть стандартной библиотеки - что делает его большой плюс для портативности

2

Чтобы проверить, если два dicts равны ключей и значений:

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) 

Если вы хотите, чтобы вернуть значения, различаются, написать его по-разному:

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

Вы бы назвать его дважды, т.е.

dict1_minus_d2(d1,d2).extend(dict1_minus_d2(d2,d1)) 
Смежные вопросы