Defaultdict imbriqué de defaultdict


129

Existe-t-il un moyen de faire d'un defaultdict également la valeur par défaut pour le defaultdict? (ie defaultdict récursif de niveau infini?)

Je veux pouvoir faire:

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

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

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

Il existe des recettes qui peuvent le faire. Mais cela peut-il être fait simplement en utilisant les arguments normaux defaultdict?

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

Je finirai probablement par utiliser le modèle de groupe , mais quand j'ai réalisé que je ne savais pas comment faire cela, cela m'a intéressé.



2
Pas vraiment ... Ajout d'informations à la question pour indiquer pourquoi. Bien que ce soit une question utile.
Corley Brigman

Réponses:


168

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 également le faire 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)

1
En effet, un exemple parfait, merci. Pourriez-vous s'il vous plaît l'étendre au cas, que les données sont chargées de json dans defaultdict de defaultdict?
David Belohrad

4
Une note. Si vous essayez d'utiliser ce code pendant le décapage lambdane fonctionnera pas.
Viacheslav Kondratiuk

167

Les autres réponses ici vous indiquent comment créer un " defaultdictqui contient" une infinité " defaultdict, mais elles ne parviennent pas à répondre à ce que je pense avoir été votre besoin initial qui était simplement d'avoir un defaultdict à deux profondeurs.

Vous cherchiez peut-être:

defaultdict(lambda: defaultdict(dict))

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

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

3
defaultdict (lambda: defaultdict (liste)) La forme correcte?
Yuvaraj Loganathan

Oups, oui, la lambdaforme est correcte - parce que le defaultdict(something)retourne un objet de type dictionnaire, mais defaultdictattend un appelable! Je vous remercie!
Chris W.

4
Cela a été marqué comme un doublon possible d'une autre question ... mais ce n'était pas ma question initiale. Je savais comment créer un defaultdict à deux niveaux; ce que je ne savais pas, c'était comment le rendre récursif. Cette réponse est, en fait, similaire à stackoverflow.com/questions/5029934/…
Corley Brigman

Un inconvénient de l'approche lambda est que les objets qu'elle génère ne peuvent pas être décapés ... mais vous pouvez contourner ce dict(result)
problème

54

Il y a une astuce astucieuse pour faire cela:

tree = lambda: defaultdict(tree)

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


22

Similaire à la solution de BrenBarn, mais ne contient pas le nom de la variable treedeux fois, donc cela fonctionne 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 xavec x = tree().


Pour la defversion, nous pouvons utiliser la portée de fermeture de fonction pour protéger la structure de données de la faille où les instances existantes cessent de fonctionner si le treenom est rebondi. Cela ressemble à ceci:

from collections import defaultdict

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

4
je vais devoir penser à celui-ci (c'est un peu plus complexe). mais je pense que votre point est que si x = tree (), mais que quelqu'un vient plus tard et fait tree = None, celui-ci fonctionnerait toujours, et ce ne serait pas le cas?
Corley Brigman

11

Je proposerais également une implémentation plus de style POO, qui prend en charge l'imbrication infinie ainsi que correctement formatée 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': {}}

1
Soigné ! J'ai ajouté le passthrough de *argset **kwargsqui lui permet de fonctionner comme le defaultdict, à savoir de créer un dict avec des arguments de mots clés. Ceci est utile pour passer NestedDefaultDictenjson.load
Ciprian Tomoiagă

0

voici une fonction récursive pour convertir un dict par défaut récursif en un dict normal

def defdict_to_dict(defdict, finaldict):
    # pass in an empty dict for finaldict
    for k, v in defdict.items():
        if isinstance(v, defaultdict):
            # new level created and that is the new value
            finaldict[k] = defdict_to_dict(v, {})
        else:
            finaldict[k] = v
    return finaldict

defdict_to_dict(my_rec_default_dict, {})

0

J'ai basé ceci de la réponse d'Andrew ici. Si vous cherchez à charger des données à partir d'un json ou d'un dict existant dans le defaultdict de nester, consultez cet exemple:

def nested_defaultdict(existing=None, **kwargs):
    if existing is None:
        existing = {}
    if not isinstance(existing, dict):
        return existing
    existing = {key: nested_defaultdict(val) for key, val in existing.items()}
    return defaultdict(nested_defaultdict, existing, **kwargs)

https://gist.github.com/nucklehead/2d29628bb49115f3c30e78c071207775

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.