繁体   English   中英

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

[英]Python find if two dictionaries have the same partitioning of keys by value

最Python化的方法是执行以下操作:假设我有两个字典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

进行一些更改,我只能得到:

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

一种基于@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

编辑:修复了@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))

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

一种方法是使用itertools.groupby对密钥进行分组,但这将要求首先对项目进行排序。 但是, python 3不支持对异构列表进行排序 ,因此我们将不得不使用“ 如何在Python 3.x中获得类似2.x的排序行为”中的答案之一

我选择@Fred的答案是因为我们不在乎排序顺序,并且它是最简单的编码。

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM