Defaultdict bersarang dari defaultdict

129

Apakah ada cara untuk membuat defaultdict juga menjadi default untuk defaultdict? (Yaitu default default rekursif tingkat tak terbatas?)

Saya ingin dapat melakukan:

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

Jadi, saya bisa melakukannya x = defaultdict(defaultdict), tetapi itu hanya level kedua:

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

Ada resep yang bisa melakukan ini. Tetapi dapatkah itu dilakukan hanya dengan menggunakan argumen defaultdict yang normal?

Catatan ini menanyakan bagaimana cara melakukan defaultdict rekursif tingkat infinite, jadi berbeda dengan Python: defaultdict of defaultdict? , yang merupakan cara melakukan defaultdict dua tingkat.

Saya mungkin hanya akan berakhir menggunakan pola tandan , tetapi ketika saya menyadari saya tidak tahu bagaimana melakukan ini, itu membuat saya tertarik.

Corley Brigman
sumber
Kemungkinan duplikat Python: defaultdict of defaultdict?
malioboro
2
Tidak juga ... menambahkan info ke pertanyaan untuk menunjukkan alasannya. Padahal itu adalah pertanyaan yang bermanfaat.
Corley Brigman

Jawaban:

168

Untuk jumlah level yang berubah-ubah:

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

Tentu saja Anda juga bisa melakukan ini dengan lambda, tetapi saya menemukan lambda kurang bisa dibaca. Bagaimanapun akan terlihat seperti ini:

rec_dd = lambda: defaultdict(rec_dd)
Andrew Clark
sumber
1
Memang contoh yang sempurna, terima kasih. Bisakah Anda memperluasnya ke case, bahwa data dimuat dari json ke defaultdict of defaultdict?
David Belohrad
4
Satu catatan. Jika Anda mencoba menggunakan kode ini saat pengawetan lambdatidak akan berhasil.
Viacheslav Kondratiuk
167

Jawaban lain di sini memberi tahu Anda cara membuat defaultdictyang berisi "tak terhingga banyaknya" defaultdict, tetapi mereka gagal menjawab apa yang saya pikir mungkin adalah kebutuhan awal Anda yang hanya memiliki standar default dua kedalaman.

Anda mungkin telah mencari:

defaultdict(lambda: defaultdict(dict))

Alasan mengapa Anda lebih suka konstruksi ini adalah:

  • Ini lebih eksplisit daripada solusi rekursif, dan karenanya lebih mudah dipahami oleh pembaca.
  • Ini memungkinkan "daun" defaultdictmenjadi sesuatu selain dari kamus, misalnya ,: defaultdict(lambda: defaultdict(list))ataudefaultdict(lambda: defaultdict(set))
Chris W.
sumber
3
defaultdict (lambda: defaultdict (list)) Bentuk yang benar?
Yuvaraj Loganathan
Ooops, ya, lambdaformnya benar - karena defaultdict(something)mengembalikan objek seperti kamus, tetapi defaultdictmengharapkan callable! Terima kasih!
Chris W.
4
Ini ditandai sebagai kemungkinan duplikat dari pertanyaan lain ... tapi itu bukan pertanyaan awal saya. Saya tahu cara membuat defaultdict dua tingkat; apa yang saya tidak tahu adalah bagaimana membuatnya rekursif. Jawaban ini, pada kenyataannya, mirip dengan stackoverflow.com/questions/5029934/…
Corley Brigman
Salah satu kelemahan dari pendekatan lambda adalah bahwa objek yang dihasilkannya tidak dapat diasamkan ... tetapi Anda dapat menyiasatinya dengan melakukan casting ke reguler dict(result)sebelum acar
CpILL
54

Ada trik bagus untuk melakukan itu:

tree = lambda: defaultdict(tree)

Kemudian Anda bisa membuatnya xdengan x = tree().

BrenBarn
sumber
22

Mirip dengan solusi BrenBarn, tetapi tidak mengandung nama variabel treedua kali, sehingga berfungsi bahkan setelah perubahan ke kamus variabel:

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

Kemudian Anda dapat membuat masing-masing xdengan x = tree().


Untuk defversi, kita bisa menggunakan lingkup penutupan fungsi untuk melindungi struktur data dari cacat di mana instans yang ada berhenti bekerja jika treenamanya kembali. Ini terlihat seperti ini:

from collections import defaultdict

def tree():
    def the_tree():
        return defaultdict(the_tree)
    return the_tree()
Poin
sumber
4
Saya harus memikirkan yang ini (ini sedikit lebih rumit). tetapi saya pikir maksud Anda adalah jika melakukan x = tree (), tetapi kemudian seseorang datang kemudian dan apakah tree = Tidak ada, ini masih akan bekerja, dan itu tidak akan terjadi?
Corley Brigman
11

Saya juga akan mengusulkan implementasi lebih banyak gaya OOP, yang mendukung bersarang tanpa batas serta diformat dengan benar repr.

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

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

Pemakaian:

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
sumber
1
Rapi! Saya menambahkan passthrough *argsdan **kwargsyang memungkinkannya berfungsi seperti defaultdict, yaitu membuat dict dengan argumen kata kunci. Ini berguna untuk melintas NestedDefaultDictkejson.load
Ciprian Tomoiagă
0

di sini adalah fungsi rekursif untuk mengubah dict default rekursif ke 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, {})
XD
sumber
0

Saya mendasarkan jawaban Andrew ini di sini. Jika Anda mencari untuk memuat data dari json atau dikt yang ada ke dalam nerd defaultdict lihat contoh ini:

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
sumber