web-dev-qa-db-fra.com

Defaultdict imbriqué de defaultdict

Existe-t-il un moyen de faire de defaultdict la valeur par défaut de defaultdict? (c'est-à-dire defaultdict récursif de niveau infini?)

Je veux pouvoir faire:

x = defaultdict(...stuff...)
x[0][1][0]
{}

Donc, je peux faire x = defaultdict(defaultdict), mais ce n'est qu'un deuxième niveau:

x[0]
{}
x[0][0]
KeyError: 0

Il y a des recettes qui peuvent faire ceci. Mais peut-on le faire simplement en utilisant les arguments normaux defaultdict?

Notez que ceci demande comment faire un defaultdict récursif de niveau infini, donc distinct de Python: defaultdict de defaultdict?, qui était comment faire un defaultdict à deux niveaux.

Je vais probablement finir par utiliser le motif , mais lorsque j'ai réalisé que je ne savais pas comment faire cela, cela m'a intéressé.

99
Corley Brigman

Pour un nombre arbitraire de niveaux:

def rec_dd():
    return defaultdict(rec_dd)

>>> x = rec_dd()
>>> x['a']['b']['c']['d']
defaultdict(<function rec_dd at 0x7f0dcef81500>, {})
>>> print json.dumps(x)
{"a": {"b": {"c": {"d": {}}}}}

Bien sûr, vous pouvez aussi faire cela avec un lambda, mais je trouve que les lambdas sont moins lisibles. Dans tous les cas, cela ressemblerait à ceci:

rec_dd = lambda: defaultdict(rec_dd)
136
Andrew Clark

Les autres réponses ici vous expliquent comment créer un defaultdict qui contient "infiniment beaucoup" defaultdict, mais elles ne répondent pas à votre besoin initial, qui était simplement d'avoir deux -depth defaultdict.

Vous avez peut-être été à la recherche de:

defaultdict(lambda: defaultdict(dict))

Les raisons pour lesquelles vous pourriez préférer cette construction sont:

  • C'est plus explicite que la solution récursive, et donc probablement plus compréhensible pour le lecteur.
  • Cela permet à la "feuille" de defaultdict d'être autre chose qu'un dictionnaire, par exemple,: defaultdict(lambda: defaultdict(list)) ou defaultdict(lambda: defaultdict(set))
131
Chris W.

Il y a un truc astucieux pour faire ça:

tree = lambda: defaultdict(tree)

Ensuite, vous pouvez créer votre x avec x = tree().

41
BrenBarn

Semblable à la solution de BrenBarn, mais ne contenant pas le nom de la variable tree, il fonctionne donc même après des modifications du dictionnaire de variables:

tree = (lambda f: f(f))(lambda a: (lambda: defaultdict(a(a))))

Ensuite, vous pouvez créer chaque nouveau x avec x = tree().


Pour la version def, nous pouvons utiliser l'étendue de fermeture de fonction pour protéger la structure de données de la faille dans laquelle les instances existantes cessent de fonctionner si le nom tree est un rebond. Cela ressemble à ceci:

from collections import defaultdict

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

Je proposerais également une implémentation plus stylée en POO, qui supporte l'imbrication infinie ainsi que le formatage correct repr.

class NestedDefaultDict(defaultdict):
    def __init__(self, *args, **kwargs):
        super(NestedDefaultDict, self).__init__(NestedDefaultDict, *args, **kwargs)

    def __repr__(self):
        return repr(dict(self))

Usage:

my_dict = NestedDefaultDict()
my_dict['a']['b'] = 1
my_dict['a']['c']['d'] = 2
my_dict['b']

print(my_dict)  # {'a': {'b': 1, 'c': {'d': 2}}, 'b': {}}
2