如何合并字典并从匹配的键中收集值?
- 2024-12-02 08:41:00
- admin 原创
- 268
问题描述:
我有多个这样的字典(或键值对序列):
d1 = {key1: x1, key2: y1}
d2 = {key1: x2, key2: y2}
作为一个新的字典,我怎样才能有效地获得这样的结果?
d = {key1: (x1, x2), key2: (y1, y2)}
另请参阅:如何在 Python 中创建具有重复键的字典?。
解决方案 1:
这是一个通用的解决方案,可以处理任意数量的字典,但有时键仅存在于部分字典中:
from collections import defaultdict
d1 = {1: 2, 3: 4}
d2 = {1: 6, 3: 7}
dd = defaultdict(list)
for d in (d1, d2): # you can list as many input dicts as you want here
for key, value in d.items():
dd[key].append(value)
print(dd) # result: defaultdict(<type 'list'>, {1: [2, 6], 3: [4, 7]})
解决方案 2:
假设所有键始终存在于所有字典中:
ds = [d1, d2]
d = {}
for k in d1.iterkeys():
d[k] = tuple(d[k] for d in ds)
注意:在 Python 3.x 中使用以下代码:
ds = [d1, d2]
d = {}
for k in d1.keys():
d[k] = tuple(d[k] for d in ds)
如果 dic 包含 numpy 数组:
ds = [d1, d2]
d = {}
for k in d1.keys():
d[k] = np.concatenate(list(d[k] for d in ds))
解决方案 3:
即使两个字典中的键不同,此函数也会合并两个字典:
def combine_dict(d1, d2):
return {
k: tuple(d[k] for d in (d1, d2) if k in d)
for k in set(d1.keys()) | set(d2.keys())
}
例子:
d1 = {
'a': 1,
'b': 2,
}
d2 = {
'b': 'boat',
'c': 'car',
'd': 'donkey',
}
combine_dict(d1, d2)
# Returns: {
# 'a': (1,),
# 'b': (2, 'boat'),
# 'c': ('car',),
# 'd': ('donkey'),
# }
解决方案 4:
dict1 = {'m': 2, 'n': 4}
dict2 = {'n': 3, 'm': 1}
确保键的顺序相同:
dict2_sorted = {i:dict2[i] for i in dict1.keys()}
keys = dict1.keys()
values = zip(dict1.values(), dict2_sorted.values())
dictionary = dict(zip(keys, values))
给出:
{'m': (2, 1), 'n': (4, 3)}
解决方案 5:
如果你只有 d1 和 d2,
from collections import defaultdict
d = defaultdict(list)
for a, b in d1.items() + d2.items():
d[a].append(b)
解决方案 6:
您可以使用以下一种方法,即使两个字典没有相同的键,它也能起作用:
d1 = {'a':'test','b':'btest','d':'dreg'}
d2 = {'a':'cool','b':'main','c':'clear'}
d = {}
for key in set(list(d1.keys()) + list(d2.keys())):
try:
d.setdefault(key,[]).append(d1[key])
except KeyError:
pass
try:
d.setdefault(key,[]).append(d2[key])
except KeyError:
pass
print(d)
这将生成以下输入:
{'a': ['test', 'cool'], 'c': ['clear'], 'b': ['btest', 'main'], 'd': ['dreg']}
解决方案 7:
使用预计算密钥
def merge(dicts):
# First, figure out which keys are present.
keys = set().union(*dicts)
# Build a dict with those keys, using a list comprehension to
# pull the values from the source dicts.
return {
k: [d[k] for d in dicts if k in d]
for k in keys
}
这本质上是 Flux 的答案,针对输入字典列表进行了概括。
这个set().union
技巧的工作原理是将所有源字典中的键合并为一个集合。union
a 上的方法set
(我们从一个空字典开始)可以接受任意数量的参数,并将每个输入与原始集合合并;并且它可以接受其他可迭代对象(它不需要其他set
参数) - 它将对它们进行迭代并查找所有唯一元素。由于对 a 进行迭代dict
会产生其键,因此可以直接将它们传递给该union
方法。
在已知所有输入的键都相同的情况下,可以简化这一点:keys
可以进行硬编码(或从其中一个输入推断),并且if
列表推导中的检查变得没有必要:
def merge(dicts):
return {
k: [d[k] for d in dicts]
for k in dicts[0].keys()
}
这类似于 blubb 的答案,但使用字典理解而不是显式循环来构建最终结果。
我们也可以尝试类似 Mahdi Ghelichi 的回答:
def merge(dicts):
values = zip(*(d.values() for d in ds))
return dict(zip(dicts[0].keys(), values))
这应该在 Python 3.5 及以下版本中有效:在程序的同一运行期间,具有相同键的字典将以相同的顺序存储它们(如果再次运行程序,可能会得到不同的顺序,但仍然是一致的)。在 3.6 及更高版本中,字典保留其插入顺序(尽管它们仅在 3.7 及更高版本的规范中保证这样做)。因此,输入字典可以以不同的顺序具有相同的键,这将导致第一个组合错误的值。我们可以通过“排序”输入字典(使用一致顺序的键重新创建它们,例如)来解决这个问题。(在旧版本中,这将是额外的工作,没有净效果。)但是,这增加了复杂性,并且这种双重压缩方法实际上并没有比使用字典理解的先前方法提供任何优势。zip
`[{k:d[k] for k in dicts[0].keys()} for d in dicts]`
明确构建结果,动态发现密钥
就像 Eli Bendersky 的回答一样,但作为一个函数:
from collections import defaultdict
def merge(dicts):
result = defaultdict(list)
for d in dicts:
for key, value in d.items():
result[key].append(value)
return result
这将生成一个defaultdict
,即标准库定义的子类dict
。仅使用内置字典的等效代码可能如下所示:
def merge(dicts):
result = {}
for d in dicts:
for key, value in d.items():
result.setdefault(key, []).append(value)
return result
使用列表以外的其他容器类型
预计算键方法可以很好地生成元组;[d[k] for d in dicts if k in d]
用替换列表理解tuple(d[k] for d in dicts if k in d)
。这会将生成器表达式传递给tuple
构造函数。(没有“元组理解”。)
由于元组是不可变的并且没有方法,因此应通过将替换append
为 来修改显式循环方法。但是,如果存在大量键重复,则此方法的性能可能会很差,因为它每次都必须创建一个新的元组。最好先生成列表,然后使用 之类的方法转换最终结果。.append(value)
`+= (value,)`{k: tuple(v) for (k, v) in merged.items()}
可以进行类似的修改来获取集合(尽管有一个集合理解,使用{}
),Numpy 数组等。例如,我们可以用容器类型概括这两种方法,如下所示:
def merge(dicts, value_type=list):
# First, figure out which keys are present.
keys = set().union(*dicts)
# Build a dict with those keys, using a list comprehension to
# pull the values from the source dicts.
return {
k: value_type(d[k] for d in dicts if k in d)
for k in keys
}
和
from collections import defaultdict
def merge(dicts, value_type=list):
# We stick with hard-coded `list` for the first part,
# because even other mutable types will offer different interfaces.
result = defaultdict(list)
for d in dicts:
for key, value in d.items():
result[key].append(value)
# This is redundant for the default case, of course.
return {k:value_type(v) for (k, v) in result}
如果输入值已经是序列
人们通常不想将源中的值包装到新列表中,而是希望获取所有值都已经是列表的输入,然后在输出中连接这些列表(或连接元组或一维 Numpy 数组、组合集合等)。
这仍然是一个微不足道的修改。对于预先计算的键,使用嵌套列表推导,按顺序获得平坦结果:
def merge(dicts):
keys = set().union(*dicts)
return {
k: [v for d in dicts if k in d for v in d[k]]
# Alternately:
# k: [v for d in dicts for v in d.get(k, [])]
for k in keys
}
你可能会想到使用 连接sum
原始列表推导的结果。不要这样做 - 当有大量重复键时,它的性能会很差。内置函数sum
未针对序列进行优化(并且将明确禁止“求和”字符串),并且会尝试在内部通过每次添加创建一个新列表。
使用显式循环方法,使用.extend
而不是.append
:
from collections import defaultdict
def merge(dicts):
result = defaultdict(list)
for d in dicts:
for key, value in d.items():
result[key].extend(value)
return result
列表的方法extend
接受任何可迭代的对象,因此它将适用于具有值元组的输入 - 当然,它仍然在输出中使用列表;当然,这些可以转换回来,如前所示。
如果输入每个都有一个项目
此问题的一个常见版本涉及输入字典,每个字典都有一个键值对。或者,输入可能是(key, value)
元组(或列表)。
当然,上述方法仍然有效。对于元组输入,首先将它们转换为字典,例如[{k:v} for (k, v) in tuples]
,允许直接使用。或者,可以修改显式迭代方法以直接接受元组,例如 Victoria Stuart 的回答:
from collections import defaultdict
def merge(pairs):
result = defaultdict(list)
for key, value in pairs:
result[key].extend(value)
return result
(代码被简化了,因为当只有一个键值对并且已经直接提供时,不需要迭代键值对。)
但是,对于这些单项情况,按键对值进行排序然后使用可能会更好itertools.groupby
。在这种情况下,使用元组会更容易。看起来像:
from itertools import groupby
def merge(tuples):
grouped = groupby(tuples, key=lambda t: t[0])
return {k: [kv[1] for kv in ts] for k, ts in grouped}
这里,t
用作输入中一个元组的名称。grouped
迭代器将提供“键”值k
(被分组的元组共有的第一个元素)和ts
该组中元组的迭代器对。然后我们从中的键值kv
对中提取值,从中创建一个列表,并将其用作结果字典中键ts
的值。k
当然,要以这种方式合并单项字典,首先要将它们转换为元组。对于单项字典列表,一种简单的方法是[next(iter(d.items())) for d in dicts]
。
解决方案 8:
如果你安装了 pandas 并且所有字典中的所有键都相同,那么你可以在一行中完成:
import pandas as pd
d1 = {key1: x1, key2: y1}
d2 = {key1: x2, key2: y2}
new_dict = pd.DataFrame([d1,d2]).to_dict('list')
解决方案 9:
假设有两个具有完全相同键的字典,下面是最简洁的做法(两种解决方案都应该使用 python3)。
d1 = {'a': 1, 'b': 2, 'c':3}
d2 = {'a': 5, 'b': 6, 'c':7}
# get keys from one of the dictionary
ks = [k for k in d1.keys()]
print(ks)
['a', 'b', 'c']
# call values from each dictionary on available keys
d_merged = {k: (d1[k], d2[k]) for k in ks}
print(d_merged)
{'a': (1, 5), 'b': (2, 6), 'c': (3, 7)}
# to merge values as list
d_merged = {k: [d1[k], d2[k]] for k in ks}
print(d_merged)
{'a': [1, 5], 'b': [2, 6], 'c': [3, 7]}
如果有两本字典有一些共同的键,但有几个不同的键,则应该准备所有键的列表。
d1 = {'a': 1, 'b': 2, 'c':3, 'd': 9}
d2 = {'a': 5, 'b': 6, 'c':7, 'e': 4}
# get keys from one of the dictionary
d1_ks = [k for k in d1.keys()]
d2_ks = [k for k in d2.keys()]
all_ks = set(d1_ks + d2_ks)
print(all_ks)
['a', 'b', 'c', 'd', 'e']
# call values from each dictionary on available keys
d_merged = {k: [d1.get(k), d2.get(k)] for k in all_ks}
print(d_merged)
{'d': [9, None], 'a': [1, 5], 'b': [2, 6], 'c': [3, 7], 'e': [None, 4]}
解决方案 10:
有一个很棒的图书馆,funcy
只需一行简短的指令就能满足您的需求。
from funcy import join_with
from pprint import pprint
d1 = {"key1": "x1", "key2": "y1"}
d2 = {"key1": "x2", "key2": "y2"}
list_of_dicts = [d1, d2]
merged_dict = join_with(tuple, list_of_dicts)
pprint(merged_dict)
输出:
{'key1': ('x1', 'x2'), 'key2': ('y1', 'y2')}
更多信息请点击这里:funcy -> join_with。
解决方案 11:
def merge(d1, d2, merge):
result = dict(d1)
for k,v in d2.iteritems():
if k in result:
result[k] = merge(result[k], v)
else:
result[k] = v
return result
d1 = {'a': 1, 'b': 2}
d2 = {'a': 1, 'b': 3, 'c': 2}
print merge(d1, d2, lambda x, y:(x,y))
{'a': (1, 1), 'c': 2, 'b': (2, 3)}
解决方案 12:
如果键是嵌套的:
d1 = { 'key1': { 'nkey1': 'x1' }, 'key2': { 'nkey2': 'y1' } }
d2 = { 'key1': { 'nkey1': 'x2' }, 'key2': { 'nkey2': 'y2' } }
ds = [d1, d2]
d = {}
for k in d1.keys():
for k2 in d1[k].keys():
d.setdefault(k, {})
d[k].setdefault(k2, [])
d[k][k2] = tuple(d[k][k2] for d in ds)
产量:
{'key1': {'nkey1': ('x1', 'x2')}, 'key2': {'nkey2': ('y1', 'y2')}}
解决方案 13:
修改这个答案来创建一个元组字典(OP要求的),而不是列表字典:
from collections import defaultdict
d1 = {1: 2, 3: 4}
d2 = {1: 6, 3: 7}
dd = defaultdict(tuple)
for d in (d1, d2): # you can list as many input dicts as you want here
for key, value in d.items():
dd[key] += (value,)
print(dd)
以上内容打印如下:
defaultdict(<class 'tuple'>, {1: (2, 6), 3: (4, 7)})
解决方案 14:
来自 blubb 的回答:
您还可以直接使用每个列表中的值形成元组
ds = [d1, d2]
d = {}
for k in d1.keys():
d[k] = (d1[k], d2[k])
如果你对元组有特定的排序,这可能会很有用
ds = [d1, d2, d3, d4]
d = {}
for k in d1.keys():
d[k] = (d3[k], d1[k], d4[k], d2[k]) #if you wanted tuple in order of d3, d1, d4, d2
解决方案 15:
使用以下方法我们可以合并两个具有相同键的字典。
def update_dict(dict1: dict, dict2: dict) -> dict:
output_dict = {}
for key in dict1.keys():
output_dict.update({key: []})
if type(dict1[key]) != str:
for value in dict1[key]:
output_dict[key].append(value)
else:
output_dict[key].append(dict1[key])
if type(dict2[key]) != str:
for value in dict2[key]:
output_dict[key].append(value)
else:
output_dict[key].append(dict2[key])
return output_dict
输入:d1 = {key1:x1,key2:y1} d2 = {key1:x2,key2:y2}
输出:{'key1':['x1','x2'],'key2':['y1','y2']}
解决方案 16:
dicts = [dict1,dict2,dict3]
out = dict(zip(dicts[0].keys(),[[dic[list(dic.keys())[key]] for dic in dicts] for key in range(0,len(dicts[0]))]))
解决方案 17:
紧凑的可能性
d1={'a':1,'b':2}
d2={'c':3,'d':4}
context={**d1, **d2}
context
{'b': 2, 'c': 3, 'd': 4, 'a': 1}
扫码咨询,免费领取项目管理大礼包!