Defaultdict nidificato di defaultdict


129

C'è un modo per rendere un defaultdict anche quello predefinito per defaultdict? (vale a dire defaultdict ricorsivo di livello infinito?)

Voglio essere in grado di fare:

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

Quindi, posso farlo x = defaultdict(defaultdict), ma è solo un secondo livello:

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

Ci sono ricette che possono farlo. Ma può essere fatto semplicemente usando i normali argomenti defaultdict?

Nota che questo sta chiedendo come eseguire un defaultdict ricorsivo di livello infinito, quindi è distinto da Python: defaultdict di defaultdict? , che era come eseguire un defaultdict a due livelli.

Probabilmente finirò per usare il modello a grappolo , ma quando mi sono reso conto che non sapevo come farlo, mi ha interessato.



2
Non proprio ... aggiunto informazioni alla domanda per indicare il perché. Questa è una domanda utile.
Corley Brigman,

Risposte:


168

Per un numero arbitrario di livelli:

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": {}}}}}

Ovviamente potresti farlo anche con un lambda, ma trovo che i lambda siano meno leggibili. In ogni caso sarebbe simile a questo:

rec_dd = lambda: defaultdict(rec_dd)

1
Davvero un esempio perfetto, grazie. Potresti estenderlo al caso in cui i dati vengano caricati da json in defaultdict of defaultdict?
David Belohrad

4
Una nota. Se stai cercando di usare questo codice mentre il decapaggio lambdanon funzionerà.
Viacheslav Kondratiuk,

167

Le altre risposte qui ti dicono come creare un file defaultdictche contiene "infinitamente molti" defaultdict, ma non riescono a rispondere a quello che penso possa essere stato il tuo bisogno iniziale che era semplicemente avere un defaultdict a due profondità.

Potresti aver cercato:

defaultdict(lambda: defaultdict(dict))

I motivi per cui potresti preferire questo costrutto sono:

  • È più esplicito della soluzione ricorsiva e quindi probabilmente più comprensibile per il lettore.
  • Ciò consente alla "foglia" di defaultdictessere qualcosa di diverso da un dizionario, ad esempio ,: defaultdict(lambda: defaultdict(list))odefaultdict(lambda: defaultdict(set))

3
defaultdict (lambda: defaultdict (elenco)) La forma corretta?
Yuvaraj Loganathan,

Ops, sì, il lambdamodulo è corretto, perché defaultdict(something)restituisce un oggetto simile a un dizionario, ma si defaultdictaspetta un callable! Grazie!
Chris W.,

4
Questo è stato contrassegnato come possibile duplicato di un'altra domanda ... ma non era la mia domanda originale. Sapevo come creare un defaultdict a due livelli; quello che non sapevo era come renderlo ricorsivo. Questa risposta è, in effetti, simile a stackoverflow.com/questions/5029934/…
Corley Brigman

Uno svantaggio dell'approccio lambda è che gli oggetti che genera non possono essere decapati ... ma puoi aggirare questo lanciando su un normale dict(result)prima del sottaceto
CpILL

54

C'è un trucco ingegnoso per farlo:

tree = lambda: defaultdict(tree)

Quindi puoi creare il tuo xcon x = tree().


22

Simile alla soluzione di BrenBarn, ma non contiene treedue volte il nome della variabile , quindi funziona anche dopo le modifiche al dizionario delle variabili:

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

Quindi è possibile creare ogni nuovo xcon x = tree().


Per la defversione, possiamo usare l'ambito di chiusura della funzione per proteggere la struttura dei dati dal difetto in cui le istanze esistenti smettono di funzionare se il treenome è di rimbalzo. Sembra così:

from collections import defaultdict

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

4
dovrò pensarci (è un po 'più complesso). ma penso che il tuo punto sia che se x = tree (), ma poi qualcuno viene più tardi e fa tree = None, questo funzionerebbe ancora, e non lo farebbe?
Corley Brigman,

11

Proporrei anche un'implementazione in stile OOP, che supporti il ​​nesting infinito e formattato correttamente repr.

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

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

Uso:

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
Pulito! Ho aggiunto il passthrough di *argse **kwargsche gli consente di funzionare come il defaultdict, vale a dire creare un dict con argomenti di parole chiave. Questo è utile per passare NestedDefaultDictajson.load
Ciprian Tomoiagă il

0

ecco una funzione ricorsiva per convertire un dict predefinito ricorsivo in un dict normale

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

Ho basato qui la risposta di Andrew qui. Se stai cercando di caricare i dati da un json o un dict esistente nel nd defaultdict vedi questo esempio:

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

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.