Python查找两个字典是否按值对键进行相同的分区

时间:2019-06-26 02:31:47

标签: python dictionary

执行以下操作的最Python方式是什么: 假设我有2个字典AB。现在,字典的常规python相等性将检查每个字典中的值和键是否相同,如果对于字典的每个元素都成立,则它们是否相等。我想对此进行修改以使字典相等,如果对于A中所有具有相同值的键集,该集合中的每个元素在B中具有相同值,但不一定相同像A中一样。

示例:

A = {'A':1, 'B':4, 'C':1}
B = {'A':9, 'B':2, 'C':9}

这里A == B。本质上,这个字典代表一组集合,我想在其上实现集合相等性。

我的尝试

def eq(a,b):
    if not a.keys() == b.keys():
        return False
    for grouping in ({k for k in a.keys() if a[k] == v} for v in a.values()):
        if not len(set(b[x] for x in grouping)) == 1:
            return False
    return True

我不太喜欢这种方法,因为它不会短路,因为必须消耗整个生成器才能将其转换为集合。想法是将第一个集合划分为组,以使每个组中的每个元素都具有相同的值。然后,我要确保对于每个分组,该分组中的元素的值在另一组中都是相同的。

修改 很抱歉,我无法更清楚地解释它,我将给出更多示例。一种更简单的思考方式是:我可以将任何字典转换为一组集合,如下所示:

A = {'A':3, 'B':3, 'C':3, 'R':4, 'T':4}
A = {{'A', 'B', 'C'}, {'R', 'T'}}
B = {'A':[], 'B':[], 'C':[], 'R':"", 'T':""}
B = {{'A', 'B', 'C'}, {'R', 'T'}}
A == B

4 个答案:

答案 0 :(得分:4)

有些更改,我只能进入:

def eq(a,b):
    if not a.keys() == b.keys():
        return False
    for x, y in zip(a.values(), b.values()):
        if not sorted([key for key in a.keys() if a[key] == x]) == sorted([key for key in b.keys() if b[key] == y]):
            return False
    return True

但是稍微干净一点的是:

def eq(a,b):
    d1 = {}
    d2 = {}
    for (x, y), (i, j) in zip(a.items(), b.items()):
        d1.setdefault(y, []).append(x)
        d2.setdefault(j, []).append(i)
    return [sorted(i) for i in d1.values()] == [sorted(i) for i in d2.values()]

或更短:

def eq(a,b):
    d1 = {y: sorted([i for i in a.keys() if a[i] == y]) for x, y in a.items()}
    d2 = {y: sorted([i for i in b.keys() if b[i] == y]) for x, y in b.items()}
    return list(d1.values()) == list(d2.values())

答案 1 :(得分:2)

基于@pault建议的一种方法是创建键值的字典,然后查看两个字典的值是否以相同的方式组合在一起。

我还对反向字典的值进行排序以照顾顺序,以及比较它们时的最终值列表。

from collections import defaultdict

def eq(A, B):

    rev_A = defaultdict(list)
    rev_B = defaultdict(list)

    #Create the reverse dictionary
    for k, v in A.items():
        #If v is a list, convert it to tuple to make a hashable key
        if isinstance(v, list):
            rev_A[tuple(v)].append(k)
        else:
            rev_A[v].append(k)

    for k, v in B.items():
        if isinstance(v, list):
            rev_B[tuple(v)].append(k)
        else:
            rev_B[v].append(k)

    #Sort the values of reverse dictionary
    for k, v in rev_A.items():
        rev_A[k] = sorted(v)

    for k, v in rev_B.items():
        rev_B[k] = sorted(v)

    #See if the values of both dictionaries group in same fashion
    return list(sorted(rev_A.values())) == list(sorted(rev_B.values()))

A = {'A':1, 'B':4, 'C':1}
B = {'A':9, 'B':2, 'C':9}

print(eq(A,B))

A = {'A':3, 'B':3, 'C':3, 'R':4, 'T':4}
B = {'C':8, 'R':6, 'T':6, 'A':8, 'B':8}

print(eq(A,B))

A = {'A':3, 'B':3, 'C':3, 'R':4, 'T':4}

B = {'A':[], 'B':[], 'C':[], 'R':"", 'T':""}
print(eq(A,B))

输出将为

True
True
True

答案 2 :(得分:2)

编辑:修复了@pault指出的问题。尽管由于b中的值不可散列,该特定输入现在会引发错误...

由于OP提到他们的原始方法不会短路,因此我将尝试给出一种可行的方法。这种方法确实要求ab中的值是可哈希的。

不过,我没有对此进行分析。无论如何,这可能取决于输入的性质。具体来说,如果ab中的值可以被散列,但是效率很低,那么这种方法当然会受到影响。

另一个想法:如果两个字典相等(在此定义下)或接近,那么此实现将需要比较python循环中的所有元素,这可能比其他实现慢。但是,如果它们可能大相径庭,允许短路工作,那么这种方法可能会显示出优势。

编辑:添加了一个参数encoding以强制哈希某些对象。当然,这取决于所使用的编码会产生一些副作用,例如[]()被认为是相等的,而具有不同顺序的相等字典被认为是不相等的。

def eq(a, b, encoding = None):
    if len(a) != len(b): return False
    mapping = {}
    value_set = set()
    for k, v_a in a.items():
        v_b = b.get(k)
        if v_b is None: return False
        if encoding: v_a, v_b = encoding(v_a), encoding(v_b)
        if v_a in mapping:
            if mapping[v_a] != v_b: return False
        elif v_b in value_set: return False
        else:
            mapping[v_a] = v_b
            value_set.add(v_b)
    return True

用法:

import json
A = {'A':3, 'B':3, 'C':3, 'R':4, 'T':4}
B = {'A':[], 'B':[], 'C':[], 'R':"", 'T':""}
print(eq(A, B, encoding = json.dumps))

答案 3 :(得分:1)

如果值不可散列,则其他答案将中断。另一种方法是根据值对键进行分组,并检查两个字典的组是否相等。

执行此操作的一种方法是使用itertools.groupby对键进行分组,但这将要求首先对项进行排序。但是,python 3 does not support sorting a heterogeneous list,因此我们将不得不使用How can I get 2.x-like sorting behaviour in Python 3.x?的答案之一。

我选择了@Fred's answer,因为我们不在乎排序顺序,而且它最容易编码。

from itertools import groupby
from operator import itemgetter
from numbers import Real
from decimal import Decimal

# from https://stackoverflow.com/a/26663384/5858851
def motley(value):
    numeric = Real, Decimal
    if isinstance(value, numeric):
        typeinfo = numeric
    else:
        typeinfo = type(value)

    try:
        x = value < value
    except TypeError:
        value = repr(value)

    return repr(typeinfo), value

def eq(A, B):
    def get_key_groups(X):
        return set(
            tuple(map(itemgetter(0), g)) 
            for i, g in groupby(
                sorted(X.items(), key=lambda x: motley(x[1])), 
                key=itemgetter(1)
            )
        )
    return get_key_groups(A) == get_key_groups(B)

一些测试:

A = {'A':1, 'B':4, 'C':1}
B = {'A':9, 'B':2, 'C':9}
eq(A, B)
#True

A = {'A':3, 'B':3, 'C':3, 'R':4, 'T':4}
B = {'A':[], 'B':[], 'C':[], 'R':"", 'T':""}
eq(A, B)
#True

A = {'A':3, 'B':2, 'C':3, 'R':4, 'T':4}
B = {'A':[], 'B':[], 'C':[], 'R':"", 'T':""}
eq(A, B)
#False