Как сжать три списка во вложенный дикт - PullRequest
0 голосов
/ 20 декабря 2018

У меня есть три списка:

z1 = ['A', 'A', 'B', 'B']
z2 = ['k1', 'k2', 'k1', 'k2']
z3 = ['v1', 'v2', 'v3', 'v4']

и когда я пишу:

print(dict(zip(z2, z3)))

это мой вывод:

{'k2': 'v4', 'k1': 'v3'}

И я ожидаю этого:

{'A':{'k1': 'v1', 'k2': 'v2'} , 'B':{'k1': 'v3', 'k2': 'v4'}}

Подскажите, пожалуйста, как мне получить ожидаемый результат?

Ответы [ 3 ]

0 голосов
/ 20 декабря 2018

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

>>> from itertools import groupby
>>> from operator import itemgetter
>>> keyf = itemgetter(0)
>>> dict((k, dict(v2 for _,v2 in v)) for k, v in groupby(zip(z1, zip(z2,z3)), key=keyf))
{'A': {'k2': 'v2', 'k1': 'v1'}, 'B': {'k2': 'v4', 'k1': 'v3'}}

Этотолько настолько коротким, насколько это возможно, потому что он использует тот факт, что z1 уже отсортирован.Если это не так, вам нужно отсортировать вывод zip с помощью той же ключевой функции, прежде чем передать его в groupby.

dict((k, dict(v2 for _,v2 in v))
       for k, v in groupby(sorted(zip(z1, zip(z2,z3)),
                                  key=keyf),
                           key=keyf))

Разбить, как это работает ...

  1. zip(z1, zip(z2, ze)) создает пары ключ-значение для внешнего диктанта:

    [('A', ('k1', 'v1')),
     ('A', ('k2', 'v2')),
     ('B', ('k1', 'v3')),
     ('B', ('k2', 'v4'))]
    
  2. groupby эффективно связывает каждую клавишу (Aили B) с его кортежами:

    [('A', <itertools._grouper object at 0x100f656d0>),
     ('B', <itertools._grouper object at 0x100f655d0>)]
    

    Каждая _grouper является итерацией, содержащей все пары ключ / значение с одним и тем же ключом.

  3. dict(v2 for _,v2 in v) извлекает только пары ключ / значение из _grouper s, оставляя после себя ключ, который мы уже можем получить из первого элемента кортежей, возвращенного groupby.

0 голосов
/ 20 декабря 2018

Ради полноты вы можете использовать dict.setdefault, избегая импорта за счет крошечных накладных расходов на создание и возврат пустого словаря на каждой итерации.

d = {}
for x, y, z in zip(z1, z2, z3):
    d.setdefault(x,{})[y] = z

print(d)
# {'A': {'k1': 'v1', 'k2': 'v2'}, 'B': {'k1': 'v3', 'k2': 'v4'}}

Другое решение (не рекомендуется) использует itertools.groupby:

d = {}
for k, g in groupby(enumerate(zip(z2, z3)), key=lambda x: z1[x[0]]):
    _, b = zip(*g)
    d[k] = dict(b)

print(d)
# {'A': {'k1': 'v1', 'k2': 'v2'}, 'B': {'k1': 'v3', 'k2': 'v4'}}
0 голосов
/ 20 декабря 2018

Функция zip() может принимать более двух итераций.Таким образом, вы можете использовать zip(z1, z2, z3) вместо zip(z2, z3).Тем не менее, вам все равно нужно сгруппировать элементы, так как простая упаковка dict() не будет работать, поскольку она не может обрабатывать вложенные словари, необходимые для трех кортежей.

Чтобы правильно сгруппировать элементы, я бы использовал collections.defaultdict():

from collections import defaultdict

z1 = ['A', 'A', 'B', 'B']
z2 = ['k1', 'k2', 'k1', 'k2']
z3 = ['v1', 'v2', 'v3', 'v4']

d = defaultdict(dict)
for x, y, z in zip(z1, z2, z3):
    d[x][y] = z

print(d)
# defaultdict(<class 'dict'>, {'A': {'k1': 'v1', 'k2': 'v2'}, 'B': {'k1': 'v3', 'k2': 'v4'}})

Вышеописанное работает, потому что defaultdict(dict) инициализирует словарь для несуществующих ключей,Он обрабатывает создание словаря для ключей для вас.

Кроме того, если вы заключите конечный результат в dict:

print(dict(d))
# {'A': {'k1': 'v1', 'k2': 'v2'}, 'B': {'k1': 'v3', 'k2': 'v4'}}

Примечание: defaultdict - это просто подкласс dict, поэтому выможно относиться к нему так же, как к обычному словарю.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...