объединение нескольких словарей Python - PullRequest
35 голосов
/ 23 февраля 2012

Я должен объединить список словаря 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)

Может ли он быть представлен более элегантно / оптимизировано?

Примечание Я нашел еще один вопрос на SO, но он касается слияния ровно 2 словарей.

Ответы [ 10 ]

32 голосов
/ 23 февраля 2012

Вы можете перебирать словари напрямую - не нужно использовать range. Метод dict setdefault ищет ключ и возвращает значение, если оно найдено. Если он не найден, он возвращает значение по умолчанию, а также назначает это значение по умолчанию для ключа.

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)

Также, как проницательно заметил Свен Марнах , вы, похоже, не хотите дублирования значений в своих списках. В этом случае 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)
23 голосов
/ 23 февраля 2012
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)
12 голосов
/ 23 февраля 2012

Объедините ключи всех диктов, и для каждого ключа соберите список значений:

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], чтобы построить список значений из всех диктов для этого ключа.

Поскольку вам кажется, что только одно уникальное значение 1009 *вместо каждой клавиши вы можете использовать наборы:

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)
3 голосов
/ 23 февраля 2012

Никогда не забывайте, что стандартные библиотеки имеют множество инструментов для работы с диктовками и итерациями:

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) согласно ответу Стивена Румбальски.

2 голосов
/ 23 февраля 2012

Это может быть немного элегантнее:

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)

ОБНОВЛЕНИЕ: внесено изменение, предложенное Свеном

ОБНОВЛЕНИЕ: изменено, чтобы избежать дубликатов (спасибо Марцину и Стивену)

1 голос
/ 19 марта 2014

Для одного пользователя может быть использовано следующее:

{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 и новее.)

0 голосов
/ 04 ноября 2018

Если вы предполагаете, что интересующие вас ключи находятся на одном и том же вложенном уровне, вы можете рекурсивно обойти каждый словарь и создать новый словарь, используя этот ключ, эффективно объединяя их.

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

Примечание: я предполагаю, что лист в каждой ветви - это какой-то список, но вы, очевидно, можете изменить логику, чтобы сделать все, что необходимо для вашей ситуации.

0 голосов
/ 03 февраля 2018

Мое решение аналогично предложенному @senderle, но вместо цикла for я использовал map

super_dict = defaultdict(set)
map(lambda y: map(lambda x: super_dict[x].add(y[x]), y), dicts)
0 голосов
/ 21 мая 2016

Кажется, что большинство ответов, использующих понимание, не настолько читабельны.В случае, если кто-то потеряется в беспорядке ответов выше, это может быть полезно (хотя и очень поздно ...).Просто зациклите предметы каждого дикта и поместите их в отдельный.

super_dict = {key:val for d in dicts for key,val in d.items()}
0 голосов
/ 23 февраля 2012

Я немного опоздал с игрой, но сделал это в 2 строки без каких-либо зависимостей, кроме самого питона:

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]}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...