我想要合并两个词:
a = {"name": "john",
"phone":"123123123",
"owns": {"cars": "Car 1", "motorbikes": "Motorbike 1"}}
b = {"name": "john",
"phone":"123",
"owns": {"cars": "Car 2"}}
如果a
和b
在同一嵌套级别上具有公共密钥,则结果应为列表,其中包含两个值,并将其指定为共享密钥的值。 / p>
结果应如下所示:
{"name": "john",
"phone":["123123123","123"],
"owns": {"cars": ["Car 1", "Car 2"], "motorbikes": "Motorbike 1"}}
使用a.update(b)
无法正常工作,因为它会使用共享值a
覆盖b
的共享值,因此结果如下:
{'name': 'john', 'phone': '123', 'owns': {'cars': 'Car 2'}}
目标是合并dicts而不覆盖,并保留与特定密钥相关的所有信息(在任何一个dicts中)。
答案 0 :(得分:5)
使用递归,您可以构建一个字典理解来实现它。
此解决方案还考虑到您可能希望稍后合并两个以上的词典,在这种情况下展平值列表。
def update_merge(d1, d2):
if isinstance(d1, dict) and isinstance(d2, dict):
# Unwrap d1 and d2 in new dictionary to keep non-shared keys with **d1, **d2
# Next unwrap a dict that treats shared keys
# If two keys have an equal value, we take that value as new value
# If the values are not equal, we recursively merge them
return {
**d1, **d2,
**{k: d1[k] if d1[k] == d2[k] else update_merge(d1[k], d2[k])
for k in {*d1} & {*d2}}
}
else:
# This case happens when values are merged
# It bundle values in a list, making sure
# to flatten them if they are already lists
return [
*(d1 if isinstance(d1, list) else [d1]),
*(d2 if isinstance(d2, list) else [d2])
]
示例:
a = {"name": "john", "phone":"123123123",
"owns": {"cars": "Car 1", "motorbikes": "Motorbike 1"}}
b = {"name": "john", "phone":"123", "owns": {"cars": "Car 2"}}
update_merge(a, b)
# {'name': 'john',
# 'phone': ['123123123', '123'],
# 'owns': {'cars': ['Car 1', 'Car 2'], 'motorbikes': 'Motorbike 1'}}
合并两个以上对象的示例:
a = {"name": "john"}
b = {"name": "jack"}
c = {"name": "joe"}
d = update_merge(a, b)
d = update_merge(d, c)
d # {'name': ['john', 'jack', 'joe']}
答案 1 :(得分:0)
使用集合和东西,也可以合并任意数量的字典:
from functools import reduce
import operator
# Usage: merge(a, b, ...)
def merge(*args):
# Make a copy of the input dicts, can be removed if you don't care about modifying
# the original dicts.
args = list(map(dict.copy, args))
# Dict to store the result.
out = {}
for k in reduce(operator.and_, map(dict.keys, args)): # Python 3 only, see footnotes.
# Use `.pop()` so that after the all elements of shared keys have been combined,
# `args` becomes a list of disjoint dicts that we can merge easily.
vs = [d.pop(k) for d in args]
if isinstance(vs[0], dict):
# Recursively merge nested dicts
common = merge(*vs)
else:
# Use a set to collect unique values
common = set(vs)
# If only one unique value, store that as is, otherwise use a list
common = next(iter(common)) if len(common) == 1 else list(common)
out[k] = common
# Merge into `out` the rest of the now disjoint dicts
for arg in args:
out.update(arg)
return out
假设要合并的每个字典具有相同的"结构",那么"owns"
不能是a
中的列表和{{1}中的字典}。 dict的每个元素也需要是可散列的,因为此方法使用集合来聚合唯一值。
以下内容仅适用于Python 3,因为在Python 2中,b
返回一个普通的旧列表。
dict.keys()
另一种方法是添加额外的reduce(operator.and_, map(dict.keys, args))
以将列表转换为集合:
map()
答案 2 :(得分:-1)
您可以使用itertools.groupby
和递归:
import itertools, sys
a = {"name": "john", "phone":"123123123", "owns": {"cars": "Car 1", "motorbikes": "Motorbike 1"}}
b = {"name": "john", "phone":"123", "owns": {"cars": "Car 2"}}
def condense(r):
return r[0] if len(set(r)) == 1 else r
def update_dict(c, d):
_v = {j:[c for _, c in h] for j, h in itertools.groupby(sorted(list(c.items())+list(d.items()), key=lambda x:x[0]), key=lambda x:x[0])}
return {j:update_dict(*e) if all(isinstance(i, dict) for i in e) else condense(e) for j, e in _v.items()}
print(update_dict(a, b))
输出:
{'name': 'john', 'owns': {'cars': ['Car 1', 'Car 2'], 'motorbikes': 'Motorbike 1'}, 'phone': ['123123123', '123']}