Вы можете преобразовать все элементы dict в наборы, использовать пересечение ключей от симметричных разностей в C, чтобы найти конфликты, и использовать объединение пересечения 3 наборов (общие элементы) и различий в C, чтобы получитьслияния.Рекурсивно объединяйте субдикаты, общие для A, B и C, конвертируйте субдикаты в кортежи пар элементов, чтобы их можно было хэшировать и преобразовывать в наборы, а затем конвертируйте их обратно в диктовки после слияния.
РЕДАКТИРОВАТЬ: В случае если значения dict являются не подлежащими изменению объектами, такими как набор, вам придется сериализовать значения (я рекомендую использовать pickle
в качестве сериализатора, поскольку он имеет встроенную поддержку Python), прежде чем вы сможете преобразовать элементы dict вустановить и десериализовать их после слияния:
import pickle
def merge(a, b, c):
# recursively merge sub-dicts that are common to a, b and c
for k in a.keys() & b.keys() & c.keys():
if all(isinstance(d.get(k), dict) for d in (a, b, c)):
a[k] = b[k] = c[k] = merge(a[k], b[k], c[k])
# convert sub-dicts into tuples of item pairs to allow them to be hashable
for d in a, b, c:
for k, v in d.items():
if isinstance(v, dict):
d[k] = tuple(v.items())
# convert all the dict items into sets
set_a, set_b, set_c = (set((k, pickle.dumps(v)) for k, v in d.items()) for d in (a, b, c))
# intersect keys from the symmetric set differences to c to find conflicts
for k in set(k for k, _ in set_a ^ set_c) & set(k for k, _ in set_b ^ set_c):
# it isn't really a conflict if the new values of a and b are the same
if a.get(k) != b.get(k) or (k in a) ^ (k in b):
raise ValueError("Conflict found in key %s" % k)
# merge the dicts by union'ing the differences to c with the common items
d = dict(set_a & set_b & set_c | set_a - set_c | set_b - set_c)
# convert the tuple of items back to dicts for output
for k, v in d.items():
v = pickle.loads(v)
if isinstance(v, tuple):
d[k] = dict(v)
else:
d[k] = v
return d
, чтобы:
C = {"x": 0, "y": 0}
A = {"x": 1, "y": 0} # Edit x, but not y
B = {"x": 0, "y": 1} # Edit y, but not x
print(merge(A, B, C))
C = {"x": 0}
A = {"x": 0, "y": 0} # Add y, keep x untouched
B = {} # Delete x
print(merge(A, B, C))
C = {"x": 0}
A = {"x": 1} # Edit x
B = {"x": 1} # Edit x with the same value
print(merge(A, B, C))
C = {"deeper": {"x": 0, "y": {3, 4}}}
A = {"deeper": {"x": {1, 2}, "y": {4, 3}}} # Edit deeper["x"], but not deeper["y"]
B = {"deeper": {"x": 0, "y": 1}} # Edit deeper["y"], but not deeper["x"]
print(merge(A, B, C))
C = {"deeper": 1}
A = {"deeper": {"x": 0, "y": 1}} # Edit deeper and turn it into a dict
B = {"deeper": 1, "x": 2} # Add x, keep deeper untouched
print(merge(A, B, C))
C = {"deeper": {"x": 0, "y": 1}}
A = {"deeper": {"x": 0, "y": 1}} # Keep deeper untouched
B = {"deeper": 1} # Turn deeper into a scalar
print(merge(A, B, C))
выдало:
{'x': 1, 'y': 1}
{'y': 0}
{'x': 1}
{'deeper': {'x': {1, 2}, 'y': 1}}
{'deeper': {'x': 0, 'y': 1}, 'x': 2}
{'deeper': 1}
, а:
C = {"x": 0}
A = {"x": 1} # Edit x
B = {"x": 2} # Edit x with a different value
print(merge(A, B, C))
повысит:
ValueError: Conflict found in key x
и:
C = {"deeper": {"x": 0, "y": 1}}
A = {"deeper": {"x": 0, "y": 2}} # Edit deeper["y"], but not deeper["x"]
B = {"deeper": 1} # Turn deeper into a scalar
print(merge(A, B, C))
повысит:
ValueError: Conflict found in key deeper