Объединение нескольких словарей Python
Мне нужно объединить список словарей python. Например,
dicts[0] = {'a':1, 'b':2, 'c':3}
dicts[1] = {'a':1, 'd':2, 'c':'foo'}
dicts[2] = {'e':57,'c':3}
super_dict = {'a':[1], 'b':[2], 'c':[3,'foo'], 'd':[2], 'e':[57]}
Я написал следующий код:
super_dict = {}
for d in dicts:
for k, v in d.items():
if super_dict.get(k) is None:
super_dict[k] = []
if v not in super_dict.get(k):
super_dict[k].append(v)
Может ли он быть представлен более элегантно/оптимизирован?
Примечание
Я нашел еще question на SO, но о том, что он объединяет ровно 2 словаря.
Ответы
Ответ 1
Вы можете напрямую перебирать словари - не нужно использовать range
. Метод setdefault
dict ищет ключ и возвращает значение, если найдено. Если не найден, он возвращает значение по умолчанию, а также присваивает это значение по умолчанию ключу.
super_dict = {}
for d in dicts:
for k, v in d.iteritems(): # d.items() in Python 3+
super_dict.setdefault(k, []).append(v)
Кроме того, вы можете использовать defaultdict
. Это просто автоматизирует setdefault
, вызывая функцию для возврата значения по умолчанию, когда ключ не найден.
import collections
super_dict = collections.defaultdict(list)
for d in dicts:
for k, v in d.iteritems(): # d.items() in Python 3+
super_dict[k].append(v)
Кроме того, как Sven Marnach, вы, похоже, не хотите дублировать значения в своих списках. В этом случае set
получает то, что вы хотите:
import collections
super_dict = collections.defaultdict(set)
for d in dicts:
for k, v in d.iteritems(): # d.items() in Python 3+
super_dict[k].add(v)
Ответ 2
from collections import defaultdict
dicts = [{'a':1, 'b':2, 'c':3},
{'a':1, 'd':2, 'c':'foo'},
{'e':57, 'c':3} ]
super_dict = defaultdict(set) # uses set to avoid duplicates
for d in dicts:
for k, v in d.items(): # use d.iteritems() in python 2
super_dict[k].add(v)
Ответ 3
Объедините ключи всех dicts, и для каждой клавиши собрайте список значений:
super_dict = {}
for k in set(k for d in dicts for k in d):
super_dict[k] = [d[k] for d in dicts if k in d]
Выражение set(k for d in dicts for k in d)
создает набор всех уникальных ключей всех словарей. Для каждого из этих уникальных ключей мы используем понимание списка [d[k] for d in dicts if k in d]
для создания списка значений из всех dicts для этого ключа.
Поскольку вы только представляете уникальное значение каждого ключа, вы можете использовать вместо него следующие команды:
super_dict = {}
for k in set(k for d in dicts for k in d):
super_dict[k] = set(d[k] for d in dicts if k in d)
Ответ 4
Никогда не забывайте, что стандартные библиотеки имеют множество инструментов для работы с dicts и итерацией:
from itertools import chain
from collections import defaultdict
super_dict = defaultdict(list)
for k,v in chain.from_iterable(d.iteritems() for d in dicts):
if v not in super_dict[k]: super_dict[k].append(v)
Обратите внимание, что if v not in super_dict[k]
можно избежать, используя defaultdict(set)
в соответствии с ответом Стивена Румбальски.
Ответ 5
Когда значения ключей находятся в списке:
from collections import defaultdict
dicts = [{'a':[1], 'b':[2], 'c':[3]},
{'a':[11], 'd':[2], 'c':['foo']},
{'e':[57], 'c':[3], "a": [1]} ]
super_dict = defaultdict(list) # uses set to avoid duplicates
for d in dicts:
for k, v in d.items(): # use d.iteritems() in python 2
super_dict[k] = list(set(super_dict[k] + v))
combined_dict = {}
for elem in super_dict.keys():
combined_dict[elem] = super_dict[elem]
combined_dict
## output: {'a': [1, 11], 'b': [2], 'c': [3, 'foo'], 'd': [2], 'e': [57]}
Ответ 6
Это может быть немного более элегантным:
super_dict = {}
for d in dicts:
for k, v in d.iteritems():
l=super_dict.setdefault(k,[])
if v not in l:
l.append(v)
UPDATE: внесено изменение, предложенное Sven
UPDATE: изменено, чтобы избежать дубликатов (спасибо Marcin и Steven)
Ответ 7
Для oneliner можно использовать следующее:
{key: {d[key] for d in dicts if key in d} for key in {key for d in dicts for key in d}}
хотя для чтения было бы полезно называть комбинированный набор клавиш:
combined_key_set = {key for d in dicts for key in d}
super_dict = {key: {d[key] for d in dicts if key in d} for key in combined_key_set}
Элегантность может обсуждаться, но лично я предпочитаю понимание за циклами.:)
(Словарь и набор понятий доступны в Python 2.7/3.1 и новее.)
Ответ 8
Мое решение аналогично предложенному @senderle, но вместо цикла for я использовал map
super_dict = defaultdict(set)
map(lambda y: map(lambda x: super_dict[x].add(y[x]), y), dicts)
Ответ 9
Если вы предполагаете, что ключи, которые вас интересуют, находятся на одном и том же вложенном уровне, вы можете рекурсивно обойти каждый словарь и создать новый словарь, используя этот ключ, эффективно объединяя их.
merged = {}
for d in dicts:
def walk(d,merge):
for key, item in d.items():
if isinstance(item, dict):
merge.setdefault(key, {})
walk(item, merge[key])
else:
merge.setdefault(key, [])
merge[key].append(item)
walk(d,merged)
Например, скажем, у вас есть следующие словари, которые вы хотите объединить.
dicts = [{'A': {'A1': {'FOO': [1,2,3]}}},
{'A': {'A1': {'A2': {'BOO': [4,5,6]}}}},
{'A': {'A1': {'FOO': [7,8]}}},
{'B': {'B1': {'COO': [9]}}},
{'B': {'B2': {'DOO': [10,11,12]}}},
{'C': {'C1': {'C2': {'POO':[13,14,15]}}}},
{'C': {'C1': {'ROO': [16,17]}}}]
Используя ключ на каждом уровне, вы должны получить что-то вроде этого:
{'A': {'A1': {'FOO': [[1, 2, 3], [7, 8]],
'A2': {'BOO': [[4, 5, 6]]}}},
'B': {'B1': {'COO': [[9]]},
'B2': {'DOO': [[10, 11, 12]]}},
'C': {'C1': {'C2': {'POO': [[13, 14, 15]]},
'ROO': [[16, 17]]}}}
Примечание: я предполагаю, что лист в каждой ветки - это какой-то список, но вы, очевидно, можете изменить логику, чтобы сделать все, что необходимо для вашей ситуации.
Ответ 10
Использование defaultdict хорошо, это также можно сделать с помощью itertools.groupby.
import itertools
# output all dict items, and sort them by key
dicts_ele = sorted( ( item for d in dicts for item in d.items() ), key = lambda x: x[0] )
# groups items by key
ele_groups = itertools.groupby( dicts_ele, key = lambda x: x[0] )
# iterates over groups and get item value
merged = { k: set( v[1] for v in grouped ) for k, grouped in ele_groups }
и, очевидно, вы можете объединить этот блок кода в однострочный стиль
merged = {
k: set( v[1] for v in grouped )
for k, grouped in (
itertools.groupby(
sorted(
( item for d in dicts for item in d.items() ),
key = lambda x: x[0]
),
key = lambda x: x[0]
)
)
}
Ответ 11
Похоже, что большинство ответов с использованием понятий не все читаемы. В случае, если кто-то потеряется в беспорядке ответов выше, это может быть полезно (хотя и очень поздно...). Просто зациклируйте элементы каждого dict и поместите их в отдельный.
super_dict = {key:val for d in dicts for key,val in d.items()}
Ответ 12
Боюсь, никто еще не опубликовал это..
d = {**one, **two, **three, **four}
print d
этого было бы достаточно..
Ответ 13
Я немного опаздываю в игру, но я сделал это в двух строках без зависимостей за пределами самого python:
flatten = lambda *c: (b for a in c for b in (flatten(*a) if isinstance(a, (tuple, list)) else (a,)))
o = reduce(lambda d1,d2: dict((k, list(flatten([d1.get(k), d2.get(k)]))) for k in set(d1.keys() + d2.keys())), dicts)
# output:
# {'a': [1, 1, None], 'c': [3, 'foo', 3], 'b': [2, None, None], 'e': [None, 57], 'd': [None, 2, None]}
Если вам не нужны вложенные списки, то:
o2 = reduce(lambda d1,d2: dict((k, [d1.get(k), d2.get(k)]) for k in set(d1.keys() + d2.keys())), dicts)
# output:
# {'a': [[1, 1], None], 'c': [[3, 'foo'], 3], 'b': [[2, None], None], 'e': [None, 57], 'd': [[None, 2], None]}