【问题标题】:Merge several dictionaries creating array on different values合并几个字典,在不同的值上创建数组
【发布时间】:2020-04-30 11:49:56
【问题描述】:

所以我有一个包含几个字典的列表,它们都有相同的键。有些字典是相同的,但一个值是不同的。如何将它们合并到 1 个具有不同值作为数组的字典中?

让我举个例子:

假设我有这本词典

[{'a':1, 'b':2,'c':3},{'a':1, 'b':2,'c':4},{'a':1, 'b':3,'c':3},{'a':1, 'b':3,'c':4}]

我想要的输出是这样的:

[{'a':1, 'b':2,'c':[3,4]},{'a':1, 'b':3,'c':[3,4]}]

我尝试过使用嵌套的for 和if,但它太贵太讨厌了,我相信一定有更好的方法。你能帮我一把吗?

假设字典上的键数量相同并且知道要合并为数组的键的名称(在这种情况下为c),我该如何为任何类型的字典执行此操作

谢谢!

【问题讨论】:

  • 这里的逻辑是什么?为什么要合并 'c' 键,而将 'b' 分开?
  • 因为我想在所有键都相同时合并,除了键 c。
  • 只是一条建议:您最好无条件地创建一个数组 - 稍后处理一些包含单个元素的数组比处理“这次是数组,这次不是”更容易

标签: python dictionary mergeddictionaries


【解决方案1】:

使用collections.defaultdict 将c 值按a 和b 元组键分组:

from collections import defaultdict

lst = [
    {"a": 1, "b": 2, "c": 3},
    {"a": 1, "b": 2, "c": 4},
    {"a": 1, "b": 3, "c": 3},
    {"a": 1, "b": 3, "c": 4},
]

d = defaultdict(list)
for x in lst:
    d[x["a"], x["b"]].append(x["c"])

result = [{"a": a, "b": b, "c": c} for (a, b), c in d.items()]

print(result)

如果lst 已经被a 和b 订购,也可以使用itertools.groupby:

from itertools import groupby
from operator import itemgetter

lst = [
    {"a": 1, "b": 2, "c": 3},
    {"a": 1, "b": 2, "c": 4},
    {"a": 1, "b": 3, "c": 3},
    {"a": 1, "b": 3, "c": 4},
]

result = [
    {"a": a, "b": b, "c": [x["c"] for x in g]}
    for (a, b), g in groupby(lst, key=itemgetter("a", "b"))
]

print(result)

或者如果lst不是按a和b排序的,我们也可以按这两个键排序:

result = [
    {"a": a, "b": b, "c": [x["c"] for x in g]}
    for (a, b), g in groupby(
        sorted(lst, key=itemgetter("a", "b")), key=itemgetter("a", "b")
    )
]

print(result)

输出:

[{'a': 1, 'b': 2, 'c': [3, 4]}, {'a': 1, 'b': 3, 'c': [3, 4]}]

更新

对于任意数量的键的更通用的解决方案:

def merge_lst_dicts(lst, keys, merge_key):
    groups = defaultdict(list)

    for item in lst:
        key = tuple(item.get(k) for k in keys)
        groups[key].append(item.get(merge_key))

    return [
        {**dict(zip(keys, group_key)), **{merge_key: merged_values}}
        for group_key, merged_values in groups.items()
    ]

print(merge_lst_dicts(lst, ["a", "b"], "c"))
# [{'a': 1, 'b': 2, 'c': [3, 4]}, {'a': 1, 'b': 3, 'c': [3, 4]}]

【讨论】:

  • 解决方案很好,但我正试图应用到我身边,我有一个字典列表,我得到 first argument must be callable or None 。另一方面,如果字典有很多键,比如 30,会怎样?
  • @Capie 嗯不确定,这些对我有用。你使用的是哪个 Python 版本?
  • 3.6.9 它适用于我的确切代码,但不适用于我的自定义数据:D
  • 如果知道所有字典都有相同数量的键并且知道应该将哪个键合并为数组,我如何才能为任何类型的具有任意数量的键的字典自动执行此操作?
  • @Mahori 感谢您的 +1。我会赞成你的回答,但我当天的赞成票已经用完了。我明天会投票。是的,在 python 中学习总是很酷的语法。我喜欢 ** 合并语法,因为我懒得与 update() 或其他东西合并。
【解决方案2】:

你可以使用临时字典来解决这个问题 -


>>>python3
Python 3.6.9 (default, Nov  7 2019, 10:44:02) 

>>> di=[{'a':1, 'b':2,'c':3},{'a':1, 'b':2,'c':4},{'a':1, 'b':3,'c':3},{'a':1, 'b':3,'c':4}]
>>> from collections import defaultdict as dd
>>> dt=dd(list) #default dict of list
>>> for d in di: #create temp dict with 'a','b' as tuple and append 'c'
...     dt[d['a'],d['b']].append(d['c'])
>>> for k,v in dt.items(): #Create final output from temp
...     ol.append({'a':k[0],'b':k[1], 'c':v})
... 
>>> ol #output
[{'a': 1, 'b': 2, 'c': [3, 4]}, {'a': 1, 'b': 3, 'c': [3, 4]}]

如果输入dict中key的数量很大,提取的过程 temp_dict 的元组可以自动化 -

如果定义合并条件的键是已知的,那么它可以只是一个常量元组,例如。

keys=('a','b') #in this case, merging happens over these keys

如果直到运行时才知道,那么我们可以使用 zip 函数获取这些密钥并设置差异,例如。

>>> di
[{'a': 1, 'b': 2, 'c': 3}, {'a': 1, 'b': 2, 'c': 4}, {'a': 1, 'b': 3, 'c': 3}, {'a': 1, 'b': 3, 'c': 4}]
>>> key_to_ignore_for_merge='c'
>>> keys=tuple(set(list(zip(*zip(*di)))[0])-set(key_to_ignore_for_merge))
>>> keys
('a', 'b')

此时,我们可以使用 map 来提取元组只作为键-

>>> dt=dd(list)
>>> for d in di:
...  dt[tuple(map(d.get,keys))].append(d[key_to_ignore_for_merge])
>>> dt
defaultdict(<class 'list'>, {(1, 2): [3, 4], (1, 3): [3, 4]})

现在,要从 default_dict 和键重新创建字典,将再次需要一些 zip 魔术!

>>> for k,v in dt.items():
...  dtt=dict(tuple(zip(keys, k)))
...  dtt[key_to_ignore_for_merge]=v
...  ol.append(dtt)
... 
>>> ol
[{'a': 1, 'b': 2, 'c': [3, 4]}, {'a': 1, 'b': 3, 'c': [3, 4]}]


此解决方案假定您只知道可能不同的键(例如“c”),其余的都是运行时。

【讨论】:

  • 如果字典的键比 a 和 b 多,比如 30,怎么办?我必须检查字典是否有 29 个键的相同值,然后将它们合并在所需键上创建数组(在这种情况下为 c)
  • 查看更新的解决方案,它是完全通用的,甚至不假设任何键顺序!
  • 我在之前的评论中提到的密钥数量呢?
  • 这适用于任意数量的键。 keys=tuple(set(list(zip(*zip(*di)))[0])-set(key_to_ignore_for_merge)) 将从字典的输入列表中提取键。可能是 30 或 3000!
猜你喜欢
  • 1970-01-01
  • 2022-10-05
  • 2021-06-16
  • 2019-05-04
  • 1970-01-01
  • 2022-12-12
  • 1970-01-01
  • 1970-01-01
  • 2016-04-17
相关资源
最近更新 更多