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.