比较两个字典:相同的键,不匹配的值

5

我目前正在尝试比较两个数据集:

dict1 = {'a':1, 'b':2, 'c':3}
dict2 = {'a':1, 'b':2, 'c':4}

在这种情况下,我希望输出的结果类似于:
set1 = set([('c', 4), ('c',3)])

由于它们的键匹配但值不匹配。

我尝试了使用交集和差集运算符的多种推导变化,但无法得到所需的输出。

非常感谢任何帮助。


2
这是不可能的:字典键不能出现两次。也许你想要一组成对的列表(元组)? - Prune
1
你想要的是打印输出,还是希望返回一个以完全相同方式打印的对象,或者只是希望返回一个与dict3类似的对象? - Rory Daulton
1
字典根据定义具有唯一的键。 - rafaelc
1
@Prune 感谢您指出这一点。我忘记了那个小细节:)。我已经修改了原帖,以便我的问题更加清晰明了。 - dyao
8个回答

9

如果您正在使用Python 2:

dict1.viewitems() ^ dict2.viewitems()

如果您正在使用Python 3:
dict1.items() ^ dict2.items()

viewitems (Python 2) 和 items (Python 3) 返回一个类似于集合的对象,我们可以使用插入符号运算符来计算对称差异。


迄今为止最有效的方法。不会创建不必要的集合。 - Padraic Cunningham
@PadraicCunningham,dict1.viewitems().symmetric_difference(dict2.items()) 在 Python 2 中会更高效一些,不是吗? - Peter Wood
1
@PeterWood dict1.viewitems() 返回一个类似于集合的对象,但不是一个真正的集合对象。因此,它没有 symetric_difference 方法。 - Hai Vu
对于Python 2来说,set(dict1.items()).symmetric_difference(dict2.items())是最有效的吗?我会进行一些测试。 - Peter Wood
@PeterWood。从内存角度来看肯定不会,就速度而言我想应该也是这样的。 - Padraic Cunningham
@PadraicCunningham 我已经更新了我的答案,并附上了Python 2的一些时间。我认为这里的答案最易读,而且足够接近最高效的答案。令我惊讶的是,最快的方法是 set(dict1.viewitems()).symmetric_difference(dict2.viewitems()) - Peter Wood

5
set(dict1.items()).symmetric_difference(dict2.items())

在Python 2中使用iteritems可以提高效率。


2
您需要的是一个MultiDict(多重字典)。它们在Python标准库中不存在,但流行的boltons包有这个功能。它们允许您将多个键存储在同一字段中。
from boltons.dictutils import MultiDict

dict1 = {'a':1, 'b':2, 'c':3}
dict2 = {'a':1, 'b':2, 'c':4}

m = MultiDict()
for k in dict1.keys():
    if dict1.get(k) != dict2.get(k):
        m.add(k, dict1.get(k))
        m.add(k, dict2.get(k))

print m
for k in m.keys():
    print k, m.getlist(k)

# OrderedMultiDict([('c', 3), ('c', 4)])
# 'c' [3, 4]

1
这在Python中是不可能的。字典是一种数据结构,其键是唯一的,因此无论值是什么,您都不能在同一个字典中两次使用相同的键。
作为替代方案,您可以为每个重复的键生成一个元组列表(键,值)。
l = [(k,v,k,dict2[k]) for k,v in dict1 if k in dict2]

抱歉,我忘了那个。输出可以是一个元组对。 - dyao

1
将字典转换为一组键值对;双向取集合差异并合并列表:
dict1 = {'a':1, 'b':2, 'c':3}
dict2 = {'a':1, 'b':2, 'c':4}

set1 = set([(k, v) for k, v in dict1.items()])
set2 = set([(k, v) for k, v in dict2.items()])
diff_set = list(set1 - set2) + list(set2-set1)
print diff_set

输出:

[('c', 3), ('c', 4)]

根据Peter Wood的观察进行编辑:

dict1 = {'a':1, 'b':2, 'c':3}
dict2 = {'a':1, 'b':2, 'c':4}

set1 = set(dict1.items())
set2 = set(dict2.items())
diff_set = set1 ^ set2
print diff_set

为什么要使用列表推导而不是生成器表达式?为什么不直接使用 set(dict1.items()) 等等?为什么不使用 set ^ set - Peter Wood
我的答案过于复杂,为了可读性和可维护性。你的建议将会提高执行效率并使代码更加简洁;它们更符合Pythonic风格。我会用你的更新编辑第二个版本。 - Prune

1
 for key, val in dict1.iteritems():
   if key in dict2 and val != dict2[key]:
     set1.add((key, val))

 for key, val in dict2.iteritems():
   if key in dict1 and val != dict1[key]:
     set1.add((key, val))

1
这会让你接近一些:


for value in zip(dict1.iteritems(), dict2.iteritems()):
    if(value[0] != value[1]):
        tuple = value
print tuple

(('c', 3), ('c', 4))


1
你可以使用 set 并执行 symmetric_difference 操作:
set(dict1.items()) ^ set(dict2.items())

编辑: 我使用 timeit 研究了Python 2的性能。

最快的方法是set(dict1.viewitems()).symmetric_difference(dict2.viewitems())

第二快,也是最易读的是dict1.viewitems() ^ dict2.viewitems()

最差的是我的答案set(dict1.items()) ^ set(dict2.items()):

>>> from timeit import timeit

>>> setup = ('dict1 = {str(i): i for i in range(1000)}; '
...          'dict2 = {str(i): (i if i % 10 else i - 1) for i in range(1000)}')

这给我们带来了两个字典,分别有1000个条目和10%的不同之处,即200个对称差异:
>>> exec(setup)
>>> len(dict1.viewitems() ^ dict2.viewitems())
200

我们将对每个案例进行30000次检查:
>>> def check(expression):
...     return timeit(expression, setup, number=30000)

时间顺序,从最好到最差:

>>> check('set(dict1.viewitems()).symmetric_difference(dict2.viewitems())')
8.233164442241105

>>> check('dict1.viewitems() ^ dict2.viewitems()')
8.242523450809585

>>> check('set(dict1.viewitems()).symmetric_difference(dict2.items())')
8.651751725357371

>>> check('set(dict1.items()).symmetric_difference(dict2.items())')
8.774394999897368

>>> check('set(dict1.items()) ^ set(dict2.items())')
9.795530728021276

这与我的答案类似,但我怀疑它会更慢,因为它构建了一个额外的集合。 - Alex Hall
@AlexHall 是的,它可能会慢一些。 - Peter Wood

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接