Zagnieżdżony defaultdict o defaultdict

129

Czy istnieje sposób, aby defaultdict był również domyślny dla defaultdict? (tj. rekurencyjny słownik default z poziomu nieskończonego?)

Chcę móc:

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

Więc mogę to zrobić x = defaultdict(defaultdict), ale to tylko drugi poziom:

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

Istnieją przepisy, które mogą to zrobić. Ale czy można to zrobić po prostu używając zwykłych argumentów defaultdict?

Zauważ, że jest to pytanie, jak wykonać rekurencyjny defaultdict na nieskończonym poziomie, więc różni się od Python: defaultdict of defaultdict? , czyli jak zrobić dwupoziomowy defaultdict.

Prawdopodobnie skończę po prostu używając wzoru wiązki , ale kiedy zdałem sobie sprawę, że nie wiem, jak to zrobić, zainteresowało mnie to.

Corley Brigman
źródło
Możliwy duplikat Pythona: defaultdict of defaultdict?
malioboro
2
Niezupełnie ... dodałem informacje do pytania, aby wskazać dlaczego. Chociaż to przydatne pytanie.
Corley Brigman

Odpowiedzi:

168

Dla dowolnej liczby poziomów:

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

Oczywiście można to również zrobić z lambdą, ale uważam, że lambdy są mniej czytelne. W każdym razie wyglądałoby to tak:

rec_dd = lambda: defaultdict(rec_dd)
Andrew Clark
źródło
1
Naprawdę doskonały przykład, dzięki. Czy mógłbyś rozszerzyć to na przypadek, że dane są ładowane z json do defaultdict of defaultdict?
David Belohrad
4
Jedna uwaga. Jeśli próbujesz użyć tego kodu podczas wytrawiania lambda, nie zadziała.
Viacheslav Kondratiuk
166

Inne odpowiedzi tutaj mówią ci, jak stworzyć, defaultdictktóry zawiera „nieskończenie wiele” defaultdict, ale nie odpowiadają one temu, co moim zdaniem mogło być twoją początkową potrzebą, która polegała na po prostu posiadaniu domyślnego słowa o dwóch głębokościach.

Być może szukałeś:

defaultdict(lambda: defaultdict(dict))

Powody, dla których możesz preferować tę konstrukcję, to:

  • Jest bardziej wyraźne niż rozwiązanie rekurencyjne, a zatem prawdopodobnie bardziej zrozumiałe dla czytelnika.
  • Dzięki temu „liść” defaultdictmoże być czymś innym niż słownik, np.: defaultdict(lambda: defaultdict(list))Lubdefaultdict(lambda: defaultdict(set))
Chris W.
źródło
3
defaultdict (lambda: defaultdict (lista)) Prawidłowa forma?
Yuvaraj Loganathan
Ups, tak, lambdaformularz jest poprawny - ponieważ defaultdict(something)zwraca obiekt podobny do słownika, ale defaultdictoczekuje wywołania! Dziękuję Ci!
Chris W.
4
To zostało oznaczone jako możliwy duplikat innego pytania ... ale nie było to moje oryginalne pytanie. Wiedziałem, jak stworzyć dwupoziomowy defaultdict; nie wiedziałem, jak sprawić, by była rekurencyjna. Ta odpowiedź jest w rzeczywistości podobna do stackoverflow.com/questions/5029934/ ...
Corley Brigman
Jedną wadą podejścia lambda jest to, że obiektów, które generuje, nie można marynować ... ale możesz to obejść, rzucając do zwykłego dict(result)
rzutu
54

Jest na to sprytna sztuczka:

tree = lambda: defaultdict(tree)

Następnie możesz utworzyć swój plik xz x = tree().

BrenBarn
źródło
22

Podobne do rozwiązania BrenBarna, ale nie zawiera treedwukrotnie nazwy zmiennej , więc działa nawet po zmianach w słowniku zmiennych:

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

Następnie możesz utworzyć każdy nowy xz x = tree().


W przypadku defwersji możemy użyć zakresu zamknięcia funkcji, aby zabezpieczyć strukturę danych przed błędem, w którym istniejące instancje przestają działać, jeśli treenazwa zostanie ponownie powiązana. To wygląda tak:

from collections import defaultdict

def tree():
    def the_tree():
        return defaultdict(the_tree)
    return the_tree()
pkt
źródło
4
będę musiał pomyśleć o tym (jest trochę bardziej złożony). ale myślę, że chodzi o to, że jeśli x = tree (), ale potem ktoś przyjdzie później i zrobi tree = None, to nadal będzie działać, a to nie?
Corley Brigman
11

Proponowałbym również implementację w stylu OOP, która obsługuje nieskończone zagnieżdżanie, a także odpowiednio sformatowaną repr.

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

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

Stosowanie:

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': {}}
Stanislav Tsepa
źródło
1
Schludnie! Dodałem przejście *argsi, **kwargsktóre pozwala mu działać jak the defaultdict, a mianowicie tworzyć dyktando z argumentami słów kluczowych. Jest to przydatne przy przechodzeniu NestedDefaultDictdojson.load
Ciprian Tomoiagă
0

tutaj jest rekurencyjna funkcja do konwersji rekurencyjnego domyślnego dyktu na normalny dykt

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, {})
Dr. XD
źródło
0

Oparłem to na odpowiedzi Andrew tutaj. Jeśli chcesz załadować dane z json lub istniejącego dyktu do domyślnego narzędzia nester, zobacz ten przykład:

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

nucklehead
źródło