Список диктов в / из диктов списков

Я ищу, чтобы переключаться между словаря списков (все одинаковой длины):

DL={'a':[0,1],'b':[2,3]}

и список словарей:

LD=[{'a':0,'b':2},{'a':1,'b':3}]

Я ищу самый чистый способ переключения между двумя формами.

14 ответов

Решение

Возможно рассмотрите возможность использования numpy:

import numpy as np

arr=np.array([(0,2),(1,3)],dtype=[('a',int),('b',int)])
print(arr)
# [(0, 2) (1, 3)]

Здесь мы получаем доступ к столбцам, индексированным по именам, например 'a', или же 'b' (вроде как DL):

print(arr['a'])
# [0 1]

Здесь мы получаем доступ к строкам по целочисленному индексу (вроде как LD):

print(arr[0])
# (0, 2)

Каждое значение в строке может быть доступно по имени столбца (вроде как LD):

print(arr[0]['b'])
# 2

Для тех из вас, кто любит умные / хакерские однострочники.

Вот DL в LD:

v = [dict(zip(DL,t)) for t in zip(*DL.values())]
print(v)

а также LD в DL:

v = {k: [dic[k] for dic in LD] for k in LD[0]}
print(v)

LD в DL немного хакер, так как вы предполагаете, что ключи одинаковы в каждом dict, Также обратите внимание, что я не одобряю использование такого кода в любой реальной системе.

Если вам разрешено использовать внешние пакеты, Pandas отлично подходит для этого:

import pandas as pd
pd.DataFrame(DL).to_dict('list')

Какие выводы:

[{'a': 0, 'b': 2}, {'a': 1, 'b': 3}]

Чтобы перейти от списка словарей, это просто:

Вы можете использовать эту форму:

DL={'a':[0,1],'b':[2,3], 'c':[4,5]}
LD=[{'a':0,'b':2, 'c':4},{'a':1,'b':3, 'c':5}]

nd={}
for d in LD:
    for k,v in d.items():
        try:
            nd[k].append(v)
        except KeyError:
            nd[k]=[v]

print nd     
#{'a': [0, 1], 'c': [4, 5], 'b': [2, 3]}

Или используйте defaultdict:

nd=cl.defaultdict(list)
for d in LD:
   for key,val in d.items():
      nd[key].append(val)

print dict(nd.items())
#{'a': [0, 1], 'c': [4, 5], 'b': [2, 3]}

Идти другим путем проблематично. Вам нужно иметь некоторую информацию о порядке вставки в список из ключей из словаря. Напомним, что порядок ключей в dict не обязательно совпадает с исходным порядком вставки.

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

nl=[]
nl_index=[]

for k in sorted(DL.keys()):
    nl.append({k:[]})
    nl_index.append(k)

for key,l in DL.items():
    for item in l:
        nl[nl_index.index(key)][key].append(item)

print nl        
#[{'a': [0, 1]}, {'b': [2, 3]}, {'c': [4, 5]}]

Если ваш вопрос был основан на любопытстве, есть ваш ответ. Если у вас есть реальные проблемы, позвольте мне предложить вам пересмотреть свои структуры данных. Ни один из них, кажется, не является очень масштабируемым решением.

Вот однострочные решения (разбросанные по нескольким строкам для удобства чтения), которые я придумал:

если dl ваш исходный список списков:

dl = {"a":[0,1],"b":[2,3]}

Тогда вот как преобразовать его в список диктовок:

ld = [{key:value[index] for key in dl.keys()}
         for index in range(max(map(len,dl.values()]

Что, если вы предполагаете, что все ваши списки имеют одинаковую длину, вы можете упростить и повысить производительность, перейдя к:

ld = [{key:value[index] for key, value in dl.items()}
         for index in range(len(dl.values()[0]))]

и вот, как преобразовать это обратно в список списков:

dl2 = {key:[item[key] for item in ld]
         for key in list(functools.reduce(
             lambda x, y: x.union(y),
             (set(dicts.keys()) for dicts in ld)
         ))
      }

Если вы используете Python 2 вместо Python 3, вы можете просто использовать reduce вместо functools.reduce там.

Вы можете упростить это, если предположите, что все ключи в вашем списке будут иметь одинаковые ключи:

dl2 = {key:[item[key] for item in ld] for key in ld[0].keys() }

cytoolz.dicttoolz.merge_with

Документы

from cytoolz.dicttoolz import merge_with

merge_with(list, *LD)

{'a': [0, 1], 'b': [2, 3]}

Версия без Cython

Документы

from toolz.dicttoolz import merge_with

merge_with(list, *LD)

{'a': [0, 1], 'b': [2, 3]}

Модуль Python pandas может дать вам понятное решение. В дополнение к ответу @chiang, решения D-to-L и L-to-D следующие:

In [1]: import pandas as pd

In [2]: DL = {'a': [0, 1], 'b': [2, 3]}

In [3]: pd.DataFrame(DL).to_dict('records')
Out[3]: [{'a': 0, 'b': 2}, {'a': 1, 'b': 3}]

In [4]: LD = [{'a': 0, 'b': 2}, {'a': 1, 'b': 3}]

In [5]: pd.DataFrame(LD).to_dict('list')
Out[5]: {'a': [0, 1], 'b': [2, 3]}

Самый чистый способ, которым я могу думать о летней пятнице. В качестве бонуса он поддерживает списки разной длины (но в этом случае DLtoLD(LDtoDL(l)) больше нет идентичности).

  1. Из списка, чтобы диктовать

    На самом деле менее чистая, чем версия defaultdict @ dwerk.

    def LDtoDL (l) :
       result = {}
       for d in l :
          for k, v in d.items() :
             result[k] = result.get(k,[]) + [v] #inefficient
       return result
    
  2. От диктата к списку

    def DLtoLD (d) :
       if not d :
          return []
       #reserve as much *distinct* dicts as the longest sequence
       result = [{} for i in range(max (map (len, d.values())))]
       #fill each dict, one key at a time
       for k, seq in d.items() :
          for oneDict, oneValue in zip(result, seq) :
         oneDict[k] = oneValue
       return result
    

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

      def dict_of_lists_to_list_of_dicts(dict_of_lists: Dict[S, List[T]]) -> List[Dict[S, T]]:
    keys = list(dict_of_lists.keys())
    list_of_values = [dict_of_lists[key] for key in keys]
    product = list(itertools.product(*list_of_values))

    return [dict(zip(keys, product_elem)) for product_elem in product]

Примеры:

      >>> dict_of_lists_to_list_of_dicts({1: [3], 2: [4, 5]})
[{1: 3, 2: 4}, {1: 3, 2: 5}]
>>> dict_of_lists_to_list_of_dicts({1: [3, 4], 2: [5]})
[{1: 3, 2: 5}, {1: 4, 2: 5}]
>>> dict_of_lists_to_list_of_dicts({1: [3, 4], 2: [5, 6]})
[{1: 3, 2: 5}, {1: 3, 2: 6}, {1: 4, 2: 5}, {1: 4, 2: 6}]
>>> dict_of_lists_to_list_of_dicts({1: [3, 4], 2: [5, 6], 7: [8, 9, 10]})
[{1: 3, 2: 5, 7: 8},
 {1: 3, 2: 5, 7: 9},
 {1: 3, 2: 5, 7: 10},
 {1: 3, 2: 6, 7: 8},
 {1: 3, 2: 6, 7: 9},
 {1: 3, 2: 6, 7: 10},
 {1: 4, 2: 5, 7: 8},
 {1: 4, 2: 5, 7: 9},
 {1: 4, 2: 5, 7: 10},
 {1: 4, 2: 6, 7: 8},
 {1: 4, 2: 6, 7: 9},
 {1: 4, 2: 6, 7: 10}]

Вот решение без использования каких-либо библиотек:

def dl_to_ld(initial):
    finalList = []
    neededLen = 0

    for key in initial:
        if(len(initial[key]) > neededLen):
            neededLen = len(initial[key])

    for i in range(neededLen):
        finalList.append({})

    for i in range(len(finalList)):
        for key in initial:
            try:
                finalList[i][key] = initial[key][i]
            except:
                pass

    return finalList

Вы можете назвать это следующим образом:

dl = {'a':[0,1],'b':[2,3]}
print(dl_to_ld(dl))

#[{'a': 0, 'b': 2}, {'a': 1, 'b': 3}]

Вот мой маленький сценарий:

a = {'a': [0, 1], 'b': [2, 3]}
elem = {}
result = []

for i in a['a']: # (1)
    for key, value in a.items():
        elem[key] = value[i]
    result.append(elem)
    elem = {}

print result

Я не уверен, что это прекрасный способ.

(1) Вы полагаете, что у вас одинаковая длина списков

Если вы не возражаете против генератора, вы можете использовать что-то вроде

def f(dl):
  l = list((k,v.__iter__()) for k,v in dl.items())
  while True:
    d = dict((k,i.next()) for k,i in l)
    if not d:
      break
    yield d

Это не так "чисто", как могло бы быть по техническим причинам: моя оригинальная реализация сделала yield dict(...), но в конечном итоге это пустой словарь, потому что (в Python 2.5) a for b in c не различает исключение StopIteration при итерации c и исключение StopIteration при оценке a,

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

Список списков ⟶ список списков

      from collections import defaultdict
from typing import TypeVar

K = TypeVar("K")
V = TypeVar("V")


def ld_to_dl(ld: list[dict[K, V]]) -> dict[K, list[V]]:
    dl = defaultdict(list)
    for d in ld:
        for k, v in d.items():
            dl[k].append(v)
    return dl

defaultdictсоздает пустой список, если он не существует при доступе к ключу.


Dict списков ⟶ список dicts

Сбор в «зубчатые» словари

      from typing import TypeVar

K = TypeVar("K")
V = TypeVar("V")


def dl_to_ld(dl: dict[K, list[V]]) -> list[dict[K, V]]:
    ld = []
    for k, vs in dl.items():
        ld += [{} for _ in range(len(vs) - len(ld))]
        for i, v in enumerate(vs):
            ld[i][k] = v
    return ld

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

Сбор только в «полные» словари

(Обычно предназначен только для списков равной длины.)

      from typing import TypeVar

K = TypeVar("K")
V = TypeVar("V")


def dl_to_ld(dl: dict[K, list[V]]) -> list[dict[K, V]]:
    ld = [dict(zip(dl.keys(), v)) for v in zip(*dl.values())]
    return ld

Это генерирует список словарейldкоторые имеют длину наименьшего списка вdl.

DL={'a':[0,1,2,3],'b':[2,3,4,5]}
LD=[{'a':0,'b':2},{'a':1,'b':3}]
Empty_list = []
Empty_dict = {}
# to find length of list in values of dictionry
len_list = 0
for i in DL.values():
    if len_list < len(i):
        len_list = len(i)

for k in range(len_list):        
    for i,j in DL.items():
        Empty_dict[i] = j[k]
    Empty_list.append(Empty_dict)
    Empty_dict = {}
LD = Empty_list
Другие вопросы по тегам