执行以下操作的最Python方式是什么:
假设我有2个字典A
和B
。现在,字典的常规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
答案 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提到他们的原始方法不会短路,因此我将尝试给出一种可行的方法。这种方法确实要求a
和b
中的值是可哈希的。
不过,我没有对此进行分析。无论如何,这可能取决于输入的性质。具体来说,如果a
或b
中的值可以被散列,但是效率很低,那么这种方法当然会受到影响。
另一个想法:如果两个字典相等(在此定义下)或接近,那么此实现将需要比较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