我有多个类似以下的字典(或键值对序列):
d1 = {key1: x1, key2: y1}
d2 = {key1: x2, key2: y2}
如何高效地将类似这样的结果作为一个新字典获取?
d = {key1: (x1, x2), key2: (y1, y2)}
我有多个类似以下的字典(或键值对序列):
d1 = {key1: x1, key2: y1}
d2 = {key1: x2, key2: y2}
如何高效地将类似这样的结果作为一个新字典获取?
d = {key1: (x1, x2), key2: (y1, y2)}
这里有一个通用的解决方案,可以处理任意数量的字典,包括只在某些字典中存在键的情况:
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]})
defaultdict
转换为普通的dict
,这样对于不存在的键等就可以具有普通的dict
行为:dd = dict(dd)
。 - Ned Deily假设所有的字典中都始终包含所有的键:
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)
如果字典包含NumPy数组:
ds = [d1, d2]
d = {}
for k in d1.keys():
d[k] = np.concatenate(list(d[k] for d in ds))
d1
不正确(它可能会忽略其他字典中的键)。 - yugr这个函数可以合并两个字典,即使这两个字典的键不同:
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'),
# }
combine_dict(d1, d2)
将返回 {'b': (2, 'boat'), 'c': ('car',), 'a': ([1, 2, 3], [1, 3])}
。 - Fluxdict1 = {'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)}
values()
中元素的顺序未定义,因此您可能会合并来自不相关键的值。 - yugrsorted(d.items())
或sorted(d.keys())
来获得可预测的结果。 - yugrd1 = {'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']}
set(d1.keys() + d2.keys())
更改为 set(list(d1.keys()) + list(d2.keys()))
吗?(适用于 Python 3.x)否则会抛出 TypeError: unsupported operand type(s) for +: 'dict_keys' and 'dict_keys'
错误。 - R4444from collections import defaultdict
d = defaultdict(list)
for a, b in d1.items() + d2.items():
d[a].append(b)
.items()
的结果在 3.x 中无法添加。 - Karl Knechteldef 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
}
set().union
技巧起作用。在一个空集上使用set
的union
方法(我们从一个空集开始)可以接受任意数量的参数,并将每个输入与原始集合进行联合;它还可以接受其他可迭代对象(不需要其他set
作为参数)- 它会遍历它们并查找所有唯一元素。由于遍历dict
会产生其键,因此它们可以直接传递给union
方法。keys
(或从其中一个输入中推断出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))
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
,该迭代器遍历该组中的元组。 然后我们从ts
中提取键值对kv
中的值,将其制作成列表,并将其用作结果字典中k
键的值。
当然,要以这种方式合并一个项的字典,首先将它们转换为元组。 为了实现这一点,对于一个由单项字典组成的列表,可以使用一个简单的方法:[next(iter(d.items())) for d in dicts]
。
import pandas as pd
d1 = {key1: x1, key2: y1}
d2 = {key1: x2, key2: y2}
new_dict = pd.DataFrame([d1,d2]).to_dict('list')
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]}
有一个很棒的库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。