我需要合并多个字典,这是我有例如:
dict1 = {1:{"a":{A}}, 2:{"b":{B}}}
dict2 = {2:{"c":{C}}, 3:{"d":{D}}}
A、B、C和D是树的叶子,比如{"info1":"value", "info2":"value2"}
字典的级别(深度)未知,可能是{2:{"c":{"z":{"y":{c}}}}}
在我的例子中,它表示一个目录/文件结构,节点是文档,叶子是文件。
我想将它们合并得到:
dict3 = {1:{"a":{A}}, 2:{"b":{B},"c":{C}}, 3:{"d":{D}}}
我不确定如何用Python轻松做到这一点。
这个版本的函数将处理N个字典,并且只处理字典——不能传递不恰当的参数,否则将引发TypeError。合并本身解释了键冲突,它不是覆盖来自合并链下的字典的数据,而是创建一组值并追加到该值;没有数据丢失。
它可能不是页面上最有效的,但它是最彻底的,当你合并2到N字典时,你不会丢失任何信息。
def merge_dicts(*dicts):
if not reduce(lambda x, y: isinstance(y, dict) and x, dicts, True):
raise TypeError, "Object in *dicts not of type dict"
if len(dicts) < 2:
raise ValueError, "Requires 2 or more dict objects"
def merge(a, b):
for d in set(a.keys()).union(b.keys()):
if d in a and d in b:
if type(a[d]) == type(b[d]):
if not isinstance(a[d], dict):
ret = list({a[d], b[d]})
if len(ret) == 1: ret = ret[0]
yield (d, sorted(ret))
else:
yield (d, dict(merge(a[d], b[d])))
else:
raise TypeError, "Conflicting key:value type assignment"
elif d in a:
yield (d, a[d])
elif d in b:
yield (d, b[d])
else:
raise KeyError
return reduce(lambda x, y: dict(merge(x, y)), dicts[1:], dicts[0])
print merge_dicts({1:1,2:{1:2}},{1:2,2:{3:1}},{4:4})
输出:{1:[1,2],2:{1:2,3:1},4:4}
我有两个字典(a和b),每个字典可以包含任意数量的嵌套字典。我想递归地合并它们,b优先于a。
将嵌套字典视为树,我想要的是:
更新a,使b中每个叶结点的每条路径都表示在a中
如果在b的对应路径中找到了叶子,则覆盖a的子树
保持所有b个叶节点都是叶节点的不变式。
现有的答案对我来说有点复杂,有些细节被束之高阁。我将以下内容整合在一起,它们通过了我的数据集的单元测试。
def merge_map(a, b):
if not isinstance(a, dict) or not isinstance(b, dict):
return b
for key in b.keys():
a[key] = merge_map(a[key], b[key]) if key in a else b[key]
return a
示例(为清晰起见,已格式化):
a = {
1 : {'a': 'red',
'b': {'blue': 'fish', 'yellow': 'bear' },
'c': { 'orange': 'dog'},
},
2 : {'d': 'green'},
3: 'e'
}
b = {
1 : {'b': 'white'},
2 : {'d': 'black'},
3: 'e'
}
>>> merge_map(a, b)
{1: {'a': 'red',
'b': 'white',
'c': {'orange': 'dog'},},
2: {'d': 'black'},
3: 'e'}
b中需要维护的路径为:
1 -> 'b' -> 'white'
2 -> 'd' -> 'black'
3 -> 'e'。
A拥有独特且不冲突的路径:
1 -> 'a' -> 'red'
1 -> 'c' -> 'orange' -> 'dog'
所以它们仍然在合并后的映射中表示。
在不影响输入字典的情况下返回一个合并。
def _merge_dicts(dictA: Dict = {}, dictB: Dict = {}) -> Dict:
# it suffices to pass as an argument a clone of `dictA`
return _merge_dicts_aux(dictA, dictB, copy(dictA))
def _merge_dicts_aux(dictA: Dict = {}, dictB: Dict = {}, result: Dict = {}, path: List[str] = None) -> Dict:
# conflict path, None if none
if path is None:
path = []
for key in dictB:
# if the key doesn't exist in A, add the B element to A
if key not in dictA:
result[key] = dictB[key]
else:
# if the key value is a dict, both in A and in B, merge the dicts
if isinstance(dictA[key], dict) and isinstance(dictB[key], dict):
_merge_dicts_aux(dictA[key], dictB[key], result[key], path + [str(key)])
# if the key value is the same in A and in B, ignore
elif dictA[key] == dictB[key]:
pass
# if the key value differs in A and in B, raise error
else:
err: str = f"Conflict at {'.'.join(path + [str(key)])}"
raise Exception(err)
return result
灵感来自@andrew cooke的解决方案
这里我有另一个稍微不同的解决方案:
def deepMerge(d1, d2, inconflict = lambda v1,v2 : v2) :
''' merge d2 into d1. using inconflict function to resolve the leaf conflicts '''
for k in d2:
if k in d1 :
if isinstance(d1[k], dict) and isinstance(d2[k], dict) :
deepMerge(d1[k], d2[k], inconflict)
elif d1[k] != d2[k] :
d1[k] = inconflict(d1[k], d2[k])
else :
d1[k] = d2[k]
return d1
默认情况下,它解决冲突,支持来自第二个字典的值,但您可以很容易地覆盖这一点,使用一些巫术,您甚至可以抛出异常。:)。
由于dictviews支持集合操作,我能够极大地简化jterrace的答案。
def merge(dict1, dict2):
for k in dict1.keys() - dict2.keys():
yield (k, dict1[k])
for k in dict2.keys() - dict1.keys():
yield (k, dict2[k])
for k in dict1.keys() & dict2.keys():
yield (k, dict(merge(dict1[k], dict2[k])))
任何将dict与非dict(技术上讲,一个带有'keys'方法的对象和一个没有'keys'方法的对象)组合的尝试都会引发AttributeError。这包括对函数的初始调用和递归调用。这正是我想要的,所以我离开了。您可以很容易地捕获递归调用抛出的AttributeErrors,然后生成您想要的任何值。