在Python中合并两个字典并保留重复键的值

12

假设我有两个字典:

dic1 =  { "first":1, "second":4, "third":8} 
dic2 =  { "first":9, "second":5, "fourth":3}

有没有一种简单的方法可以获得以下类似的内容?

dic3 =  { "first":[1,9], "second":[4,5], "third":[8], "fourth":[3]}

我使用列表来存储值,但元组也可以。


1
您确定要将仅出现在一个字典中的项目作为裸项,而不是一个项目的列表吗? - interfect
1
@interfect 好的,我明白你的意思了,我正在进行编辑。 - Netchaiev
8个回答

9
你可以使用一个 defaultdict 来保存列表,并将值附加到它们上。这种方法可以轻松地扩展到任意数量的字典。
from collections import defaultdict

dd = defaultdict(list)

dics = [dic1, dic2]
for dic in dics:
    for key, val in dic.iteritems():  # .items() in Python 3.
        dd[key].append(val)

>>> dict(dd)
{'first': [1, 9], 'fourth': [3], 'second': [4, 5], 'third': [8]}

所有单值键仍然保存在列表中,这可能是最好的方法。不过,您可以将任何长度为1的内容更改为实际值,例如

for key, val in dd.iteritems():  # .items() in Python 3.
    if len(val) == 1
        dd[key] = val[0]

2
这个实现将会使得 defaultdict 中所有的值都是 list 类型,即使在键中没有重复的值。如果期望的行为是保留单个值作为该值的类型,那么我们应该在添加之前检查键是否存在,并且只有当该键已经存在时才转换为列表。 - ctj232
1
@ctj232已经更正了问题,将它们改成了列表结构,这样做更有意义。 - Alexander

9
这里有一个简单的解决方案:将其中一个字典复制到结果中,然后迭代另一个字典的键和值,并在必要时向结果添加列表。由于只有两个字典,所以合并后的列表最多只会有2个项目。
dic1 = {"first": 1, "second": 4, "third": 8} 
dic2 = {"first": 9, "second": 5, "fourth": 3}
dic3 = dict(dic2)

for k, v in dic1.items():
    dic3[k] = [dic3[k], v] if k in dic3 else v

print(dic3) # => {'first': [9, 1], 'second': [5, 4], 'fourth': 3, 'third': 8}

如果您希望单个值变成列表(这通常是更好的设计,因为混合类型可能不太容易处理),您可以使用以下代码:
dic3 = {k: [v] for k, v in dic2.items()}

for k, v in dic1.items():
    dic3[k] = dic3[k] + [v] if k in dic3 else [v]

print(dic3) # => {'first': [9, 1], 'second': [5, 4], 'fourth': [3], 'third': [8]}

将其推广到任意数量的字典:
def merge_dicts(*dicts):
    """
    >>> merge_dicts({"a": 2}, {"b": 4, "a": 3}, {"a": 1})
    {'a': [2, 3, 1], 'b': [4]}
    """
    merged = {}
    
    for d in dicts:
        for k, v in d.items():
            if k not in merged:
                merged[k] = []

            merged[k].append(v)
    
    return merged

如果您不介意导入,可以使用 collections.defaultdict 来使代码更加简洁:
from collections import defaultdict

def merge_dicts(*dicts):
    """
    >>> merge_dicts({"a": 2}, {"b": 4, "a": 3}, {"a": 1})
    defaultdict(<class 'list'>, {'a': [2, 3, 1], 'b': [4]})
    """
    merged = defaultdict(list)
    
    for d in dicts:
        for k, v in d.items():
            merged[k].append(v)
    
    return merged

如果 OP 想要单元素列表,可以使用 [*set([dic3[k], v])] 替换三目表达式。 - Enrico Borba

3

给定:

dic1 =  { "first":1, "second":4, "third":8} 
dic2 =  { "first":9, "second":5, "fourth":3}

你可以使用 .setdefault 方法:
dic_new={}
for k,v in list(dic1.items())+list(dic2.items()):
    dic_new.setdefault(k, []).append(v)
else:
    dic_new={k:v if len(v)>1 else v[0] for k,v in dic_new.items()}  

>>> dic_new
{'first': [1, 9], 'second': [4, 5], 'third': 8, 'fourth': 3}

这将产生所需的输出。我认为将单个元素列表展平到不同的对象类型是一种不必要的复杂性。


经过编辑,这将产生所需的结果:

dic_new={}
for k,v in list(dic1.items())+list(dic2.items()):
    dic_new.setdefault(k, []).append(v)

>>> dic_new
{'first': [1, 9], 'second': [4, 5], 'third': [8], 'fourth': [3]}

3
使用集合和字典推导式
L = [d1, d2]
dups = set(d1.keys() & d2.keys())
d = {k: [L[0][k], L[1][k]] if k in dups else i[k] for i in L for k in i}
{'first': [1, 9], 'second': [4, 5], 'third': 8, 'fourth': 3}

2
一般来说,将不同键的值转换为不同的对象类型是一种不好的做法。我会建议只需执行以下操作:
def merge_values(val1, val2):
    if val1 is None:
        return [val2]
    elif val2 is None:
        return [val1]
    else:
        return [val1, val2]
dict3 = {
    key: merge_values(dic1.get(key), dic2.get(key))
    for key in set(dic1).union(dic2)
}

好的,我会编辑已有的内容以保持统一的格式。 - Netchaiev

1
创建一个新的字典dic,其键为dic1dic2的键,值为一个空列表,然后迭代dic1dic2将值附加到dic中。
dic1 =  { "first":1, "second":4, "third":8} 
dic2 =  { "first":9, "second":5, "fourth":3}

dic = {key:[] for key in list(dic1.keys()) + list(dic2.keys())}

for key in dic1.keys():
    dic[key].append(dic1[key])

for key in dic2.keys():
    dic[key].append(dic2[key])

仅限Python 2。在Python 3中,dic1.keys() + dic2.keys()会导致TypeError错误。 - dawg
将dict.keys()转换为list可与python3兼容。 我从未搜索过为什么在python3中基本函数如sum不再适用于任意类型。 对字符串列表求和非常方便。 - Scrooge McDuck
现在可以使用 dic = {key:[] for key in {k for k in list(dic1) + list(dic2)}} 来使其更加高效,这样就可以消除重复的键。 - dawg
集合转换仍将读取整个列表以创建集合。我没有进行转换,因为它会使符号更加繁琐,而且在内存方面也不会有任何收益。 - Scrooge McDuck
Python只是在试图保护你。使用sum来连接一个字符串列表将成为Shlemiel the painter算法的经典例子。连接n个字符串将会创建n-2个临时字符串。 - PM 2Ring
保护我免受将字符串的总和别名为''.join的影响?很抱歉,我无法理解该链接与sum的关系。文章中描述的冗余计算取决于+的实现;因此,如果对于Python而言是这种情况,那么使用+连接n个字符串的任何串联都是有效的join。那么,为什么不将字符串之间的+别名为二进制''。join字符串的sum,以替换低效的字符串连接器呢? - Scrooge McDuck

0

字典列表的解决方案(改编自@dawg):

dic1 =  { "first":[1], "second":[4], "third":[8]} 
dic2 =  { "first":[9], "second":[5], "fourth":[3]}
dic_new={}
for k,v in list(dic1.items())+list(dic2.items()):
    dic_new.setdefault(k, []).extend(v)
>>> dic_new
{'first': [1, 9], 'second': [4, 5], 'third': [8], 'fourth': [3]}

-1
from copy import deepcopy


def _add_value_to_list(value, lis):
    if value:
        if isinstance(value, list):
            lis.extend(value)
        else:
            lis.append(value)
    else:
        pass


def _merge_value(value_a, value_b):
    merged_value = []
    _add_value_to_list(value_a, merged_value)
    _add_value_to_list(value_b, merged_value)
    return merged_value


def _recursion_merge_dict(new_dic, dic_a, dic_b):
    if not dic_a or not dic_b:
        return new_dic
    else:
        if isinstance(new_dic, dict):
            for k, v in new_dic.items():
                new_dic[k] = _recursion_merge_dict(v, dic_a.get(k, {}), dic_b.get(k, {}))
            return new_dic
        else:
            return _merge_value(dic_a, dic_b)


def merge_dicts(dic_a, dic_b):
    new_dic = deepcopy(dic_a)
    new_dic.update(dic_b)

    return _recursion_merge_dict(new_dic, dic_a, dic_b)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接