我一直在寻找一种解决方案来找到Python中两个词典之间的对称差异。
例如,如果我有两个字典A和B,并且我想创建第三个字典C,其中包含A和B中未在另一个中找到的所有项目,或者换句话说,唯一。
我找不到规范的答案,所以我决定打开这个问题并给出我自己的答案。如果你认为你有更好的方法,我很乐意看到它。
一些数据:
a = {'a': 1, 'b':2}
b = {'b': 2, 'c':3}
期望的输出:
{'a': 1, 'c': 3}
答案 0 :(得分:8)
要获得两个字典之间的对称差异,请使用以下强健函数:
def dict_symmetric_difference(a, b):
return {k: a[k] if k in a else b[k] for k in # break here to fit without scrolling
set(a.keys()).symmetric_difference(b.keys())}
只是逻辑:
{k: a[k] if k in a else b[k] for k in set(a.keys()).symmetric_difference(b.keys())}
以下是解释函数的简单版本:
def dict_symmetric_difference(a, b):
# first make sets of the dictionary keys
keys_in_a = set(a.keys())
keys_in_b = set(b.keys())
unique_keys = keys_in_a.symmetric_difference(keys_in_b) # get the unique keys
c = {} # start an empty dictionary
for key in unique_keys: # iterate over the keys
if key in a: # if the key is from a dictionary, take the value from there.
c[key] = a[key]
else: # the key is in b dictionary, take the value from there.
c[key] = b[key]
return c
a[k] if k in a else b[k]
表达式的说明:
这是一个ternary operator,允许我这样使用它:a if condition else b
有了这个技巧,我得到了密钥的值,无论它在哪个字典中。
使用任一功能:
>>> dict_symmetric_difference({'a': 1, 'b':2}, {'b':2, 'c':3})
{'a': 1, 'c': 3}
答案 1 :(得分:5)
以下是一些对各种算法进行timeit速度测试的代码。
测试使用相同大小的成对序列。密钥是短随机字母串,在dicts之间具有不同比例的共享密钥。 dicts是由混洗列表构成的,因此即使它们包含许多共享密钥,两个dicts的底层哈希表结构应该是相当不同的。
共享密钥的确切数量是随机的,共享密钥的比例由shared
的{{1}} arg控制。
此代码的主体将在Python 2.6+和Python 3上运行。我在这台机器上安装了Python 2.6.6和Python 3.6.0(这是一台单核32位机器,运行2GB RAM旧的Debian衍生物Linux)。一些字典对称差异函数使用字典理解,这在Python 2.6中是不可用的,因此我无法在Python 2上测试这些函数。此外,make_dicts
不会运行Python 3,所以我已经评论过了。我原本打算发布Python 2.6的结果,但我不得不减少输出以适应消息大小限制。
elmex_dsd_py2
<强>输出强>
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
''' Dictionary symmetric difference
Speed tests of various implementations
See http://stackoverflow.com/q/42650081/4014959
Speed test code by PM 2Ring 2017.03.08
'''
from __future__ import print_function
from itertools import product
from random import random, seed, shuffle
from string import ascii_letters
from timeit import Timer
seed(163)
# The dict symmetric difference functions ------------------------------
def inbar_dsd_long(a, b):
# first make sets of the dictionary keys
keys_in_a = set(a.keys())
keys_in_b = set(b.keys())
# get the unique keys
unique_keys = keys_in_a.symmetric_difference(keys_in_b)
# start an empty dictionary
c = {}
# iterate over the keys
for key in unique_keys:
if key in a:
# if the key is from a dictionary, take the value from there.
c[key] = a[key]
else:
# the key is in b dictionary, take the value from there.
c[key] = b[key]
return c
def pm2r_dsd_py2(a, b):
return dict((k, a[k] if k in a else b[k]) for k in set(a.keys()) ^ set(b.keys()))
#def elmex_dsd_py2(a, b):
#symm_diff = set(a) ^ set(b)
#return dict((k, v) for k, v in a.items() + b.items() if k in symm_diff)
def raymond_dsd(a, b):
c = a.copy()
c.update(b)
for k in (a.keys() & b.keys()):
del c[k]
return c
def inbar_dsd_short(a, b):
return {k: a[k] if k in a else b[k] for k in
set(a.keys()).symmetric_difference(b.keys())}
def pm2r_dsd_py3(a, b):
return {k: a[k] if k in a else b[k] for k in a.keys() ^ b.keys()}
def evkounis_dsd(a, b):
res = {k:v for k, v in a.items() if k not in b}
res.update({k:v for k, v in b.items() if k not in a})
return res
def elmex_dsd_py3(a, b):
symm_diff = set(a) ^ set(b)
return {k: v for k, v in list(a.items()) + list(b.items()) if k in symm_diff}
funcs = (
inbar_dsd_long,
pm2r_dsd_py2,
#elmex_dsd_py2,
raymond_dsd,
inbar_dsd_short,
pm2r_dsd_py3,
evkounis_dsd,
elmex_dsd_py3,
)
# ----------------------------------------------------------------------
# Random key strings
all_keys = [''.join(t) for t in product(ascii_letters, repeat=3)]
shuffle(all_keys)
def make_dicts(size, shared):
''' Make a pair of dicts of length `size`, with random key strings.
`shared` is a real number 0 <= shared <= 1 giving the approximate
ratio of shared keys.
'''
a, b = [], []
keys = iter(all_keys)
shared_count = 0
for i in range(size):
ka = next(keys)
if random() < shared:
kb = ka
shared_count += 1
else:
kb = next(keys)
a.append((ka, i))
b.append((kb, i))
shuffle(a)
shuffle(b)
return dict(a), dict(b), shared_count
def verify(a, b):
''' Verify that all functions return the same result '''
results = [func(a, b) for func in funcs]
last = results[-1]
print(all(last == u for u in results[:-1]))
def time_test(loops, reps):
''' Print timing stats for all the functions '''
timings = []
for func in funcs:
fname = func.__name__
setup = 'from __main__ import a, b, ' + fname
cmd = '{0}(a, b)'.format(fname)
t = Timer(cmd, setup)
result = t.repeat(reps, loops)
result.sort()
timings.append((result, fname))
timings.sort()
for result, fname in timings:
print('{0:16} {1}'.format(fname, result))
# ----------------------------------------------------------------------
print('Verifying')
size = 1000
a, b, shared_count = make_dicts(size, 0.1)
print('size: {0}, shared count: {1}'.format(size, shared_count))
verify(a, b)
# Timeit tests
reps = 3
fmt = '\nsize: {0}, shared count: {1}, loops: {2}'
for shared in (0.1, 0.25, 0.5, 0.75, 0.9):
print('\nSHARED: {0:0.2f}'.format(shared))
#for size in (5, 10, 50, 100, 500, 1000, 5000, 10000, 50000):
for size in (10, 100, 1000, 10000):
a, b, shared_count = make_dicts(size, shared)
loops = 100000 // size
print(fmt.format(size, shared_count, loops))
time_test(loops, reps)
答案 2 :(得分:3)
对称差异等于联合减去交集:
>>> a = {'a': 1, 'b':2}
>>> b = {'b': 2, 'c':3}
>>> c = a.copy()
>>> c.update(b)
>>> for k in (a.keys() & b.keys()):
del c[k]
>>> c
{'a': 1, 'c': 3}
答案 3 :(得分:3)
dict.keys()
View object类似于设置,它支持^
symmetric_difference运算符。
来自文档:
键视图设置类似,因为它们的条目是唯一且可清除的。 [...]对于类似集合的视图,为...定义的所有操作 抽象基类collections.abc.Set可用(例如, ==,&lt;,或^)。
为了处理在Inbar Rose的原始解决方案中使用or
表达式产生的false-ish值的问题,我们可以使用in
测试;如果密钥不在a
,则密钥必须在b
,因此我们只需要进行1次in
测试。
def dict_symmetric_difference(a, b):
return {k: a[k] if k in a else b[k] for k in a.keys() ^ b.keys()}
a = {'a': 1, 'b':2, 'd': ''}
b = {'b': 2, 'c':3}
print(dict_symmetric_difference(a, b))
<强>输出强>
{'d': '', 'c': 3, 'a': 1}
Python 2没有字典视图对象,因此在Python 2中,您需要使用.keys()
打包set()
次调用。 2.7之前的Python版本不支持字典理解,但您可以将生成器表达式传递给dict()
构造函数,除非您运行的是真正的古老版本的Python。 / p>
这是一个可以在Python 2.4 +上正确运行的版本:
def dict_symmetric_difference(a, b):
return dict((k, a[k] if k in a else b[k]) for k in set(a.keys()) ^ set(b.keys()))
我们可以避免使用symmetric_difference
方法而不是^
运算符进行两次调用,因为各种set操作的非运算符版本将接受任何iterable作为参数。所以我们可以做到
set(a.keys()).symmetric_difference(b.keys())
而不是
set(a.keys()) ^ set(b.keys())
正如Martijn Pieters在评论中指出的那样,字典视图对象已经backported to Python 2.7。语法与Python 3略有不同,以避免破坏使用.keys
,.values
和.items
方法的代码。要获取键视图对象,请使用.viewkeys
方法。 mydict.viewkeys()
比set(mydict.keys())
更有效率。字典视图对象也具有动态的好处,即它们反映了对字典所做的任何更改,而如果对set(mydict.keys())
进行了任何更改,则必须再次调用mydict
。这不是这个代码的问题,但是当你需要它时它是一个很棒的功能。
答案 4 :(得分:1)
我就是这样做的:
A = {'a': 1, 'b': 2}
B = {'b': 2, 'c': 3}
def dict_symmetric_difference(dict_A, dict_B):
res = {k:v for k, v in dict_A.items() if k not in dict_B}
res.update({k:v for k, v in dict_B.items() if k not in dict_A})
return res
print(dict_symmetric_difference(A, B)) # {'a': 1, 'c': 3}
答案 5 :(得分:0)
很短
A = {'a': 1, 'b': 2}
B = {'b': 2, 'c': 3}
print dict((k, v) for k, v in A.items() + B.items() if k in set(A) ^ set(B))
如果您对速度感到不舒服并且怀疑Python在每次迭代时都要set(A) ^ set(B)
,您可以使用此代码:
symm_diff = set(A) ^ set(B)
print dict((k, v) for k, v in A.items() + B.items() if k in symm_diff)
答案 6 :(得分:0)
如果您使用的是 Python 3,这是一个比其他解决方案更紧凑的解决方案:
{k: v for k, v in a.items() ^ b.items()}
请注意,如果这些字典中有不可散列的类型,它不起作用。