Объединить несколько словарей, создавая массив на разные значения - PullRequest
2 голосов
/ 30 апреля 2020

Итак, у меня есть список с несколькими словарями, все они имеют одинаковые ключи. Некоторые словари одинаковы, но одно значение отличается. Как я могу объединить их в 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 в данном случае)

спасибо!

Ответы [ 2 ]

2 голосов
/ 30 апреля 2020

Вы можете использовать временный диктат для решения этой проблемы -


>>>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 велико, процесс извлечения кортежа для 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')

На данный момент мы можем использовать карту для извлечения кортежа только для ключей -

>>> 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 magi c!

>>> 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'), а отдых - это все время выполнения.

2 голосов
/ 30 апреля 2020

Используйте 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)

Можно также использовать itertools.groupby , если lst уже упорядочен по a и b:

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]}]

Обновление

Для более универсального c решения для любое количество ключей:

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]}]
...