2011-03-20 19 views
45

Tengo una lista tan grande como:¿Incumplimiento de varios niveles con profundidad variable?

[A][B1][C1]=1 
[A][B1][C2]=2 
[A][B2]=3 
[D][E][F][G]=4 

Quiero construir un diccionario de varios niveles como:

A 
--B1 
-----C1=1 
-----C2=1 
--B2=3 
D 
--E 
----F 
------G=4 

sé que si uso defaultdict recursiva Puedo escribir table[A][B1][C1]=1, table[A][B2]=2, pero esto funciona solo si codigo esas instrucciones de inserción.

Al analizar la lista, no sé cuántos [] necesito de antemano para llamar al table[key1][key2][...].

+0

fuertemente relacionada: https://stackoverflow.com/questions/16547643/convert-a-list-of-delimited-strings-to-a -tree-nested-dict-using-python –

Respuesta

94

Puede hacerlo sin ni siquiera definir una clase:

from collections import defaultdict 

nested_dict = lambda: defaultdict(nested_dict) 
nest = nested_dict() 

nest[0][1][2][3][4][5] = 6 
+2

¡esto es dulce! pero ¿qué tal si quiero que las hojas se inicialicen a través de una fábrica estándar (int, list, etc)? por ejemplo, quiero poder decir: 'tabla [0] [1] [2] [3] [4] [5] + = 1' – rikb

+0

hay una manera de hacer lo mismo con un dict incorporado y .obtener() ? –

+0

@rikb: no veo cómo establecer una profundidad fija (para diferenciar los nodos de salida) –

-2

Tiene table['A']=defaultdict().

12

Su ejemplo dice que en cualquier nivel puede haber un valor, y también un diccionario de subelementos. Se llama árbol , y hay muchas implementaciones disponibles para ellos. Este es uno:

from collections import defaultdict 
class Tree(defaultdict): 
    def __init__(self, value=None): 
     super(Tree, self).__init__(Tree) 
     self.value = value 

root = Tree() 
root.value = 1 
root['a']['b'].value = 3 
print root.value 
print root['a']['b'].value 
print root['c']['d']['f'].value 

Salidas:

1 
3 
None 

Se podría hacer algo similar al escribir la entrada en JSON y utilizando json.load leerlo como una estructura de diccionarios anidados.

+0

Creo que el constructo 'value' es innecesario, al menos con respecto al problema propuesto. Simplemente elimine las referencias a 'valor' y asigne valores directamente a las teclas del diccionario. –

+0

+1: Aunque el atributo 'valor' arg/no es realmente necesario. – martineau

+3

@Martineau @Jason. La variable de instancia 'value' es necesaria porque, de lo contrario, perderá los hijos cuando asigne directamente a un nodo (vea mi comentario a la elegante solución de Jason). Intervenir '__setitem__' proporcionaría una solución mucho más robusta, pero sería una solución demasiado complicada para los requisitos simples. – Apalala

9

lo haría con una subclase de dict que define __missing__:

>>> class NestedDict(dict): 
...  def __missing__(self, key): 
...    self[key] = NestedDict() 
...    return self[key] 
... 
>>> table = NestedDict() 
>>> table['A']['B1']['C1'] = 1 
>>> table 
{'A': {'B1': {'C1': 1}}} 

Usted no puede hacerlo directamente con defaultdict porque defaultdict expects the factory function en tiempo de inicialización, pero en tiempo de inicialización, no hay manera de describir el mismo defecto predeterminado. El constructo anterior hace lo mismo que el dict predeterminado, pero dado que se trata de una clase con nombre (NestedDict), puede referirse a sí mismo a medida que se encuentran claves faltantes. También es posible crear una subclase defaultdict y anular __init__.

+0

Eso no es suficiente. Obtendrá un error si prueba 'tabla ['A'] ['B1'] ['C1'] ['D2'] = 2'. Los nodos deben poder contener un valor ** y ** los hijos. – Apalala

+2

@Apalala: De hecho, a partir de la entrada de ejemplo de OP, parece que los nodos solo deben poder contener un valor ** o ** hijos, nunca ambos, razón por la cual @Jason y yo reclamamos el atributo 'value' de la respuesta fue innecesario. – martineau

+0

@martinau MHO es que todo se vuelve inestable (propenso a errores) a menos que se resuelva como un árbol. La sintaxis y la implementación son irrelevantes. ¿Es o no es un problema que requiere una estructura de árbol? Mi punto es que uno no debe forzar un diseño hacia una sintaxis _pretty a menos que existan razones convincentes para hacerlo. BESO. – Apalala

4

creo que la más simple aplicación de un diccionario recursiva es esto. Solo los nodos de hoja pueden contener valores.

# Define recursive dictionary 
tree = lambda: defaultdict(tree) 

Uso:

# Create instance 
mydict = tree() 

tree['a'] = 1 
tree['b']['a'] = 2 
tree['c'] 
tree['d']['a']['b'] = 0 

# Print 
import prettyprint 
prettyprint.pp(tree) 

de salida:

{ 
    "a": 1, 
    "b": { 
    "a": 1 
    }, 
    "c": {}, 
    "d": { 
    "a": { 
     "b": 0 
    } 
    } 
} 
+0

acaba de notar que mi publicación es un duplicado de # 2. Lo siento –

3

Esto es equivalente a la anterior, pero evitando la notación lambda. Tal vez más fácil de leer?

def dict_factory(): 
    return defaultdict(dict_factory) 

your_dict = dict_factory() 

También - a partir de los comentarios - si desea actualizar desde una dict existente, sólo tiene que llamar

your_dict[0][1][2].update({"some_key":"some_value"}) 

Para agregar valores a la dict.

2

Un poco diferente posibilidad de que permite que un diccionario tradicional de inicialización:

from collections import defaultdict 

def superdict(arg=()): 
    update = lambda obj, arg: obj.update(arg) or obj 
    return update(defaultdict(superdict), arg) 

Ejemplo:

>>> d = {"a":1} 
>>> sd = superdict(d) 
>>> sd["b"]["c"] = 2 
2

Dan O'Huiginn registró una solución muy agradable en su diario en 2010:

http://ohuiginn.net/mt/2010/07/nested_dictionaries_in_python.html

>>> class NestedDict(dict): 
...  def __getitem__(self, key): 
...   if key in self: return self.get(key) 
...   return self.setdefault(key, NestedDict()) 


>>> eggs = NestedDict() 
>>> eggs[1][2][3][4][5] 
{} 
>>> eggs 
{1: {2: {3: {4: {5: {}}}}}} 
+0

Me parece agradable este enfoque cuando quiero crear un diccionario anidado rápidamente. Si quiero "volver a habilitar" 'KeyError', es fácil convertir de nuevo a un diccionario estándar usando' dict() '. –

0

Para añadir a @Hugo
Para tener una profundidad máxima:

l=lambda x:defaultdict(lambda:l(x-1)) if x>0 else defaultdict(dict) 
arr = l(2) 
Cuestiones relacionadas