web-dev-qa-db-fra.com

Defaultdict à plusieurs niveaux avec une profondeur variable?

J'ai une grande liste comme:

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

Je veux construire un dict multi-niveaux comme:

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

Je sais que si j'utilise defaultdict récursif, je peux écrire table[A][B1][C1]=1, table[A][B2]=2, mais cela ne fonctionne que si je code ces instructions avec insert. 

Lors de l'analyse de la liste, je ne sais pas combien de [] il me faut au préalable d'appeler table[key1][key2][...].

54
Wei Shi

Vous pouvez le faire sans même définir une classe:

from collections import defaultdict

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

nest[0][1][2][3][4][5] = 6
130
Hugo Walter

Votre exemple indique que, à n'importe quel niveau, il peut y avoir une valeur, ainsi qu'un dictionnaire de sous-éléments. C'est ce qu'on appelle un tree , et de nombreuses implémentations sont disponibles pour eux. C'est un:

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

Les sorties:

1
3
None

Vous pouvez faire quelque chose de similaire en écrivant l'entrée en JSON et en utilisant json.load pour la lire comme une structure de dictionnaires imbriqués. 

15
Apalala

Je le ferais avec une sous-classe de dict qui définit __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}}}

Vous ne pouvez pas le faire directement avec defaultdict car defaultdict attend la fonction factory au moment de l'initialisation, mais au moment de l'initialisation, il n'est pas possible de décrire le même defaultdict. La construction ci-dessus fait la même chose que dict par défaut, mais comme il s'agit d'une classe nommée (NestedDict), elle peut se référer à elle-même en cas de découverte de clés manquantes. Il est également possible de sous-classer defaultdict et de remplacer __init__.

9
Jason R. Coombs

Je pense que la plus simple implémentation d’un dictionnaire récursif est la suivante. Seuls les nœuds terminaux peuvent contenir des valeurs.

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

Usage:

# Create instance
mydict = tree()

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

# Print
import prettyprint
prettyprint.pp(tree)

Sortie:

{
  "a": 1, 
  "b": {
    "a": 1
  }, 
  "c": {},
  "d": {
    "a": {
      "b": 0
    }
  }
}
6
Bouke Versteegh

Ceci est équivalent à ce qui précède, mais en évitant la notation lambda. Peut-être plus facile à lire?

def dict_factory():
   return defaultdict(dict_factory)

your_dict = dict_factory()

De plus, à partir des commentaires, si vous souhaitez mettre à jour un dict existant, vous pouvez simplement

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

Afin d'ajouter des valeurs au dict.

4
gabe

Dan O'Huiginn a publié une solution très agréable sur son journal 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: {}}}}}}
3
Dvd Avins

Une possibilité légèrement différente qui permet l'initialisation régulière du dictionnaire: 

from collections import defaultdict

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

Exemple:

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

Pour ajouter à @ Hugo 
Pour avoir une profondeur maximale:

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

Vous pouvez y parvenir avec une variable defaultdict récurrente.

from collections import defaultdict

def tree():
    def the_tree():
        return defaultdict(the_tree)
    return the_tree()

Il est important de protéger le nom d'usine par défaut, the_tree, ici, lors d'une fermeture (étendue de la fonction locale "privée"). Évitez d'utiliser une version lambda one-liner, qui est perturbée en raison des fermetures de liaisons late de Python , et implémentez-la avec un def à la place.

La réponse acceptée, en utilisant un lambda, a une faille dans laquelle des instances doivent s'appuyer sur le nom nested_dict existant dans une portée externe. Si, pour une raison quelconque, le nom de la fabrique ne peut pas être résolu (par exemple, il a été rebondi ou supprimé), les instances préexistantes seront également subtilement cassées:

>>> nested_dict = lambda: defaultdict(nested_dict)
>>> nest = nested_dict()
>>> nest[0][1][2][3][4][6] = 7
>>> del nested_dict
>>> nest[8][9] = 10
# NameError: name 'nested_dict' is not defined
0
wim