如何获得两个词典的对称差异

时间:2017-03-07 13:50:41

标签: python python-2.7 dictionary set

我一直在寻找一种解决方案来找到Python中两个词典之间的对称差异。

例如,如果我有两个字典A和B,并且我想创建第三个字典C,其中包含A和B中未在另一个中找到的所有项目,或者换句话说,唯一。

我找不到规范的答案,所以我决定打开这个问题并给出我自己的答案。如果你认为你有更好的方法,我很乐意看到它。

一些数据:

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

期望的输出:

{'a': 1, 'c': 3}

7 个答案:

答案 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()}

请注意,如果这些字典中有不可散列的类型,它不起作用