正如其他答案所指出的,递归算法在这里最有意义。一般来说,在使用递归时,最好创建新值而不是尝试修改任何输入数据结构。
我们需要定义每个合并步骤发生的情况。如果两个输入都是字典,那么这很容易:我们从每一侧复制唯一的键,并递归合并重复键的值。问题在于基本情况。如果我们提取一个单独的函数作为占位符,那么逻辑会更加容易理解,我们可以将两个值包装在元组中。
def merge_leaves(x, y):
return (x, y)
现在我们的逻辑核心看起来像这样:
def merge(x, y):
if not(isinstance(x, dict) and isinstance(y, dict)):
return merge_leaves(x, y)
x_keys, y_keys = x.keys(), y.keys()
result = { k: merge(x[k], y[k]) for k in x_keys & y_keys }
result.update({k: x[k] for k in x_keys - y_keys})
result.update({k: y[k] for k in y_keys - x_keys})
return result
让我们来测试一下:
>>> x = {'a': {'b': 'c', 'd': 'e'}, 'f': 1, 'g': {'h', 'i'}, 'j': None}
>>> y = {'a': {'d': 'e', 'h': 'i'}, 'f': {'b': 'c'}, 'g': 1, 'k': None}
>>> merge(x, y)
{'f': (1, {'b': 'c'}), 'g': ({'h', 'i'}, 1), 'a': {'d': ('e', 'e'), 'b': 'c', 'h': 'i'}, 'j': None, 'k': None}
>>> x
{'a': {'b': 'c', 'd': 'e'}, 'f': 1, 'g': {'h', 'i'}, 'j': None}
>>> y
{'a': {'d': 'e', 'h': 'i'}, 'f': {'b': 'c'}, 'g': 1, 'k': None}
我们可以轻松修改叶子合并规则,例如:
def merge_leaves(x, y):
try:
return x + y
except TypeError:
return Ellipsis
观察效果:
>>> merge(x, y)
{'f': Ellipsis, 'g': Ellipsis, 'a': {'d': 'ee', 'b': 'c', 'h': 'i'}, 'j': None, 'k': None}
我们还可以通过使用第三方库根据输入类型进行分派来潜在地简化这个过程。例如,使用multipledispatch,我们可以做到以下几点:
@dispatch(dict, dict)
def merge(x, y):
x_keys, y_keys = x.keys(), y.keys()
result = { k: merge(x[k], y[k]) for k in x_keys & y_keys }
result.update({k: x[k] for k in x_keys - y_keys})
result.update({k: y[k] for k in y_keys - x_keys})
return result
@dispatch(str, str)
def merge(x, y):
return x + y
@dispatch(tuple, tuple)
def merge(x, y):
return x + y
@dispatch(list, list)
def merge(x, y):
return x + y
@dispatch(int, int):
def merge(x, y):
raise ValueError("integer value conflict")
@dispatch(object, object):
return (x, y)
这样可以处理各种叶子类型的特殊情况,而无需编写自己的类型检查,并且还可以替换主递归函数中的类型检查。
d = Dict({1:{"a":{'A'}}, 2:{"b":{'B'}}}); d.update({2:{"c":{'C'}}, 3:{"d":{'D'}}}); d
=>{1: {'a': {'A'}}, 2: {'b': {'B'}, 'c': {'C'}}, 3: {'d': {'D'}}}
- bartolo-otrit