Suponha que você tenha um dicionário como:
{'a': 1,
'c': {'a': 2,
'b': {'x': 5,
'y' : 10}},
'd': [1, 2, 3]}
Como você planificaria isso em algo como:
{'a': 1,
'c_a': 2,
'c_b_x': 5,
'c_b_y': 10,
'd': [1, 2, 3]}
python
dictionary
A Timmes
fonte
fonte
Respostas:
Basicamente, da mesma maneira que você achataria uma lista aninhada, basta fazer o trabalho extra para iterar o ditado por chave / valor, criando novas chaves para o seu novo dicionário e criando o dicionário na etapa final.
fonte
isinstance
por umtry..except
bloco, isso funcionará para qualquer mapeamento, mesmo que não seja derivadodict
.collections.MutableMapping
torná-lo mais genérico. Mas para Python <2.6,try..except
é provavelmente a melhor opção.if isinstance(v, collections.MutableMapping):
paraif v and isinstance(v, collections.MutableMapping):
new_key = parent_key + sep + k if parent_key else k
assume que as chaves são sempre strings, caso contrário, ele aumentaráTypeError: cannot concatenate 'str' and [other] objects
. No entanto, você pode corrigir isso simplesmente coagindok
em string (str(k)
) ou concatenando chaves em uma tupla em vez de em uma string (tuplas também podem ser chaves de ditado).Há duas grandes considerações que o pôster original precisa considerar:
{'a_b':{'c':1}, 'a':{'b_c':2}}
resultaria em{'a_b_c':???}
. A solução abaixo evita o problema retornando uma iterável de pares.joinedKey = '_'.join(*keys)
, isso custará O (N ^ 2) tempo de execução. No entanto, se você estiver disposto a dizernextKey = previousKey+'_'+thisKey
, você recebe tempo de O (N). A solução abaixo permite fazer as duas coisas (já que você pode simplesmente concatenar todas as chaves e depois processá-las).(O desempenho provavelmente não é um problema, mas vou explicar o segundo ponto, caso alguém se importe: na implementação disso, existem inúmeras opções perigosas. Se você fizer isso de forma recursiva e produzir e reproduzir novamente, ou qualquer coisa equivalente que toque nós mais de uma vez (o que é bastante fácil de executar acidentalmente), você está realizando um trabalho potencialmente O (N ^ 2) em vez de O (N) Isso ocorre porque talvez você esteja calculando uma chave
a
ea_1
depoisa_1_i
... e, em seguida, calculandoa
entãoa_1
entãoa_1_ii
..., mas na verdade você não deveria ter que calculara_1
novamente. Mesmo se você não estiver recalculando, refazê-lo (uma abordagem de 'nível por nível') é igualmente ruim. Um bom exemplo é pensar sobre o desempenho{1:{1:{1:{1:...(N times)...{1:SOME_LARGE_DICTIONARY_OF_SIZE_N}...}}}}
)Abaixo está uma função que escrevi
flattenDict(d, join=..., lift=...)
que pode ser adaptada a muitos propósitos e pode fazer o que você deseja. Infelizmente, é bastante difícil criar uma versão lenta dessa função sem incorrer nas penalidades de desempenho acima (muitos componentes python como chain.from_iterable não são realmente eficientes, o que eu só percebi após testes extensivos de três versões diferentes desse código antes de decidir sobre este).Para entender melhor o que está acontecendo, abaixo está um diagrama para aqueles que não estão familiarizados com
reduce
(à esquerda), também conhecido como "dobra à esquerda". Às vezes, é desenhado com um valor inicial no lugar de k0 (não faz parte da lista, passado para a função). AquiJ
está a nossajoin
função. Nós pré-processamos cada k n comlift(k)
.Na verdade, é o mesmo que
functools.reduce
, mas onde nossa função faz isso em todos os caminhos-chave da árvore.Demonstração (que eu colocaria em docstring):
Atuação:
... suspiro, não pense que isso é culpa minha ...
[nota histórica sem importância devido a problemas de moderação]
Em relação à suposta duplicata de Flatten, um dicionário de dicionários (2 níveis de profundidade) de listas em Python :
A solução dessa pergunta pode ser implementada em termos desta, fazendo
sorted( sum(flatten(...),[]) )
. O inverso não é possível: embora seja verdade que os valores deflatten(...)
podem ser recuperados da suposta duplicada mapeando um acumulador de ordem superior, não é possível recuperar as chaves. (editar: também acontece que a pergunta do suposto proprietário duplicado é completamente diferente, pois ela lida apenas com dicionários com exatamente 2 níveis de profundidade, embora uma das respostas nessa página dê uma solução geral.)fonte
Ou, se você já estiver usando pandas, poderá fazê-lo da seguinte
json_normalize()
maneira:Resultado:
fonte
Se você estiver usando,
pandas
há uma função oculta empandas.io.json._normalize
1 chamadanested_to_record
que faz exatamente isso.1 Nas versões pandas
0.24.x
e uso mais antigopandas.io.json.normalize
(sem o_
)fonte
from pandas.io.json._normalize import nested_to_record
. Observe o sublinhado (_
) antesnormalize
.0.25.x
, eu atualizei a resposta. :)Aqui está um tipo de implementação "funcional" e "one-liner". É recursivo e baseado em uma expressão condicional e uma compreensão de ditado.
Teste:
fonte
('hgf',2)
para a 2ª chave em seu teste de lançaTypeError
+
operador. Para qualquer outra coisa, você precisará se adaptarprefix + separator + k
à chamada de função apropriada para compor os objetos.{'a_b':{'c':1}, 'a':{'b_c':2}}
{'name': 'Steven', 'children': [{'name': 'Jessica', 'children': []}, {'name': 'George', 'children': []}]}
Código:
Resultados:
Estou usando python3.2, atualização para sua versão do python.
fonte
lkey=''
em sua definição de função, em vez de chamar a função. Veja outras respostas a esse respeito.Que tal uma solução funcional e de alto desempenho no Python3.5?
Isso é ainda mais eficiente:
Em uso:
fonte
reduce
ótimo, caso você precise reduzir dicionários. Eu atualizei a resposta. Agora deve parecer um pouco mais pitônico.Isso não se restringe aos dicionários, mas a todos os tipos de mapeamento que implementam .items (). Além disso, é mais rápido, pois evita uma condição if. No entanto, os créditos vão para Imran:
fonte
d
não for umdict
tipo de mapeamento personalizado que não é implementadoitems
, sua função falharia naquele momento. Portanto, ele não funciona para todos os tipos de mapeamento, mas apenas para os que implementamitems()
.items
? Eu ficaria curioso para ver um.Minha solução Python 3.3 usando geradores:
fonte
Função simples para nivelar dicionários aninhados. Para Python 3, substitua
.iteritems()
por.items()
A ideia / requisito era: obter dicionários simples sem manter as chaves dos pais.
Exemplo de uso:
Manter as chaves dos pais também é simples.
fonte
Utilizando recursão, mantendo-o simples e legível por humanos:
A chamada é simples:
ou
se quisermos mudar o separador padrão.
Um pequeno colapso:
Quando a função é chamada pela primeira vez, é chamada apenas passando o
dictionary
que queremos achatar. Oaccumulator
parâmetro está aqui para dar suporte à recursão, que vemos mais adiante. Portanto, instanciamosaccumulator
um dicionário vazio, onde colocaremos todos os valores aninhados do originaldictionary
.À medida que iteramos sobre os valores do dicionário, construímos uma chave para cada valor. O
parent_key
argumento seráNone
para a primeira chamada, enquanto que para cada dicionário aninhado, ele conterá a chave apontando para ele, portanto, acrescentamos essa chave.Caso o valor que
v
a chavek
está apontando seja um dicionário, a função chama a si mesma, passando o dicionário aninhado, oaccumulator
(que é passado por referência, para que todas as alterações feitas nele sejam feitas na mesma instância) e a chavek
para que possamos pode construir a chave concatenada. Observe acontinue
declaração. Queremos pular a próxima linha, fora doif
bloco, para que o dicionário aninhado não acabe naaccumulator
chave abaixok
.Então, o que fazemos caso o valor
v
não seja um dicionário? Basta colocá-lo inalterado dentro doaccumulator
.Quando terminamos, retornamos o
accumulator
, deixando odictionary
argumento original intocado.NOTA
Isso funcionará apenas com dicionários que tenham cadeias de caracteres como chaves. Ele funcionará com objetos hashable implementando o
__repr__
método, mas produzirá resultados indesejados.fonte
Isso é semelhante à resposta de imran e ralu. Ele não usa um gerador, mas emprega recursão com um fechamento:
fonte
_flatten_dict
nunca é retornada, nem se espera que seja retornada. Talvez possa ser referido como uma subfunção ou uma função fechada .A solução de Davoud é muito boa, mas não fornece resultados satisfatórios quando o dict aninhado também contém listas de dict, mas seu código pode ser adaptado para esse caso:
fonte
type([])
para evitar uma chamada de função para cada item dodict
.isinstance(v, list)
vez dissoAs respostas acima funcionam muito bem. Apenas pensei em adicionar a função unflatten que escrevi:
Nota: Isso não leva em conta '_' já presente nas chaves, assim como as contrapartidas achatadas.
fonte
Aqui está um algoritmo para substituição elegante e no local. Testado com Python 2.7 e Python 3.5. Usando o caractere de ponto como um separador.
Exemplo:
Resultado:
Publiquei este código aqui junto com a
unflatten_json
função correspondente .fonte
Se você deseja nivelar um dicionário aninhado e desejar uma lista de todas as chaves exclusivas, aqui está a solução:
fonte
fonte
fonte
Eu estava pensando em uma subclasse de UserDict para nivelar automaticamente as chaves.
Advantages As vantagens são que as teclas podem ser adicionadas em tempo real, ou usando instanciação de ditado padrão, sem surpresa:
fonte
Usando geradores:
fonte
type(i).__name__=='dict'
pode ser substituído portype(i) is dict
ou talvez até melhorisinstance(d, dict)
(ouMapping
/MutableMapping
).Usando dict.popitem () na recursão simples como uma lista aninhada:
fonte
Não é exatamente o que o OP pediu, mas muitas pessoas estão vindo aqui procurando maneiras de nivelar dados JSON aninhados no mundo real que podem ter objetos json e matrizes json com valor-chave e objetos json dentro das matrizes e assim por diante. O JSON não inclui tuplas, portanto não precisamos nos preocupar com elas.
Encontrei uma implementação do comentário de inclusão na lista de @roneo na resposta postada por @Imran :
https://github.com/ScriptSmith/socialreaper/blob/master/socialreaper/tools.py#L8
Teste-o:
E isso faz o trabalho que eu preciso: joguei qualquer json complicado nisso e isso o achatou para mim.
Todos os créditos em https://github.com/ScriptSmith .
fonte
Na verdade, eu escrevi recentemente um pacote chamado cherrypicker para lidar com esse tipo exato de coisa, já que eu tinha que fazer isso com tanta frequência!
Acho que o código a seguir daria exatamente o que você procura:
Você pode instalar o pacote com:
... e há mais documentos e orientações em https://cherrypicker.readthedocs.io .
Outros métodos podem ser mais rápido, mas a prioridade deste pacote é fazer com que essas tarefas fáceis . Se você possui uma grande lista de objetos para achatar, também pode pedir ao CherryPicker para usar o processamento paralelo para acelerar as coisas.
fonte
Eu sempre prefiro acessar
dict
objetos via.items()
, então, para aplainar dictos, uso o seguinte gerador recursivoflat_items(d)
. Se você gostaria de terdict
novamente, simplesmente envolva-o assim:flat = dict(flat_items(d))
fonte
Variação desses dicionários Flatten aninhados, compactando chaves com max_level e redutor personalizado.
fonte
Se você não se importa com funções recursivas, aqui está uma solução. Também tomei a liberdade de incluir um parâmetro de exclusão , caso haja um ou mais valores que você deseja manter.
Código:
Uso:
Resultado:
fonte
Tentei algumas das soluções nesta página - embora não todas -, mas as que tentei falharam ao lidar com a lista aninhada de dict.
Considere um ditado como este:
Aqui está minha solução improvisada:
que produz:
Uma solução improvisada e não é perfeita.
NOTA:
não mantém ditados vazios, como o
address: {}
par k / v.não achatará os dicionários nas tuplas aninhadas - embora seja fácil adicionar usando o fato de que as tuplas python agem de maneira semelhante às listas.
fonte
Basta usar
python-benedict
, é uma subclasse dict que oferece muitos recursos, incluindo umflatten
método. É possível instalá-lo usando o pip:pip install python-benedict
https://github.com/fabiocaccamo/python-benedict#flatten
fonte