Как объединить список из нескольких словарей в словарь списков?

У меня есть следующий список словарей в Python3.x:

list_of_dictionaries = [{0:3523, 1:3524, 2:3540, 4:3541, 5:3542}, 
                        {0:7245, 1:7246, 2:7247, 3:7248, 5:7249, 6:7250},
                        {1:20898, 2:20899, 3:20900, 4:20901, 5:20902}]

В данном случае это единый список с тремя словарями.

Я хотел бы эффективно объединить это в один словарь со списками в качестве значений; вот правильный ответ:

correct = {0:[3523, 7245], 1:[3524, 7246, 20898], 2:[3540, 7247, 20899], 
               3:[7248, 20900], 4:[3541, 20901], 5:[3542, 7249, 20902], 6:[7250]}

Моей первой мыслью было такое понимание списка:

dict(pair for dictionary in list_of_dictionaries for pair in dictionary.items())

Но это неправильно, так как он не включает списки значений:

{0: 7245, 1: 20898, 2: 20899, 4: 20901, 5: 20902, 3: 20900, 6: 7250}

Я также беспокоюсь о том, как максимально эффективно создавать списки значений. Он также может не масштабироваться до больших списков/больших словарей.

Как я мог это сделать?


person ShanZhengYang    schedule 07.10.2018    source источник


Ответы (4)


defaultdict

Вы можете использовать collections.defaultdict. Ваше понимание словаря никогда не будет работать, поскольку вы не определяете никаких списков. Это, вероятно, будет более эффективным, чем использование понимания словаря, которое включает итерацию каждого словаря для каждого уникального ключа.

from collections import defaultdict

dd = defaultdict(list)

for d in list_of_dictionaries:
    for k, v in d.items():
        dd[k].append(v)

Результат:

print(dd)

defaultdict(list,
            {0: [3523, 7245],
             1: [3524, 7246, 20898],
             2: [3540, 7247, 20899],
             4: [3541, 20901],
             5: [3542, 7249, 20902],
             3: [7248, 20900],
             6: [7250]})

Понимание словаря

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

allkeys = set().union(*list_of_dictionaries)

res = {k: [d[k] for d in list_of_dictionaries if k in d] for k in allkeys}

{0: [3523, 7245],
 1: [3524, 7246, 20898],
 2: [3540, 7247, 20899],
 3: [7248, 20900],
 4: [3541, 20901],
 5: [3542, 7249, 20902],
 6: [7250]}

Временная сложность

Рассмотрим эти термины:

n = sum(map(len, list_of_dictionaries))
m = len(set().union(*list_of_dictionaries))
k = len(list_of_dictionaries)

В этом контексте defaultdict решение будет иметь сложность O(n), а понимание словаря будет иметь сложность O(mk), где mk >= н.

person jpp    schedule 07.10.2018

почему бы просто не использовать циклы for? Например:

final = {}

for i in list_of_dictionaries:
    for k in i:
        if not k in final:
            final[k] = []
        final[k].append(i[k])


print(final)

Выводит final как:

{0: [3523, 7245], 1: [3524, 7246, 20898], 2: [3540, 7247, 20899], 4: [3541, 20901], 5: [3542, 7249, 20902], 3: [7248, 20900], 6: [7250]}

person SShah    schedule 07.10.2018
comment
Простота FTW! - person slider; 08.10.2018

Используя groupby и itemgetter, мы могли бы сначала создать плоский список кортежей, представляющих keys and values каждого подслова. Затем мы можем использовать groupby в нашем отсортированном новом списке. Оттуда мы можем создать наш новый словарь, используя k и элементы в index[1] из list(g).

from itertools import groupby
from operator import itemgetter

d = {}
new_lod = sorted([(j, i[j]) for i in lod for j in i], key=itemgetter(0))
for k, g in groupby(new_lod, key=itemgetter(0)):
    d[k] = [i[1] for i in list(g)]

# {0: [3523, 7245], 1: [3524, 7246, 20898], 2: [3540, 7247, 20899], 3: [7248, 20900], 4: [3541, 20901], 5: [3542, 7249, 20902], 6: [7250]}
person vash_the_stampede    schedule 07.10.2018
comment
Список вокруг g не нужен, так как он повторяемый, и вы все равно создаете ist (как и в других ответах, использующих groupby). Лично я нахожу использование i и j трудным для чтения, поскольку они обычно используются для чисел. - person de1; 08.10.2018

Сначала вам нужно сгладить словари:

flattened_pairs = (
    pair for dictionary in list_of_dictionaries for pair in dictionary.items()
)

Затем вы можете использовать itertools.groupby для группировки значений. Ожидается, что значения будут отсортированы по ключу.

key_fn = lambda pair: pair[0]

merged = {
    k: [pair[1] for pair in g]
    for k, g in groupby(
        sorted(flattened_pairs, key=key_fn),
        key=key_fn
    )
}

print(merged)

Выход:

{0: [3523, 7245], 1: [3524, 7246, 20898], 2: [3540, 7247, 20899], 3: [7248, 20900], 4: [3541, 20901], 5: [3542, 7249, 20902], 6: [7250]}

person de1    schedule 07.10.2018
comment
Чем это отличается от моего ответа ниже? - person Ajax1234; 08.10.2018
comment
@ Ajax1234 ваш ответ появился только после того, как я закончил печатать. Я все равно отправил его на случай, если это поможет кому-то лучше понять проблему. - person de1; 08.10.2018