Python: defaultdict of defaultdict?


323

Apakah ada cara untuk memiliki defaultdict(defaultdict(int))agar kode berikut berfungsi?

for x in stuff:
    d[x.a][x.b] += x.c_int

dperlu dibangun ad-hoc, tergantung pada x.adan x.belemen.

Saya bisa menggunakan:

for x in stuff:
    d[x.a,x.b] += x.c_int

tapi kemudian saya tidak bisa menggunakan:

d.keys()
d[x.a].keys()

6
Lihat pertanyaan serupa Apa cara terbaik untuk mengimplementasikan kamus bersarang di Python? . Ada juga beberapa informasi yang mungkin berguna dalam artikel Wikipedia tentang Autovivification .
martineau

Jawaban:


571

Ya seperti ini:

defaultdict(lambda: defaultdict(int))

Argumen a defaultdict(dalam hal ini adalah lambda: defaultdict(int)) akan dipanggil ketika Anda mencoba mengakses kunci yang tidak ada. Nilai pengembaliannya akan ditetapkan sebagai nilai baru dari kunci ini, yang berarti dalam kasus kami nilai d[Key_doesnt_exist]akan defaultdict(int).

Jika Anda mencoba mengakses kunci dari default default terakhir ini yaitu d[Key_doesnt_exist][Key_doesnt_exist]akan mengembalikan 0, yang merupakan nilai pengembalian argumen dari default default yaitu int().


7
ini bekerja dengan baik! dapatkah Anda menjelaskan alasan di balik sintaks ini?
Jonathan

37
@ Jonathan: Ya tentu, argumen a defaultdict(dalam hal ini lambda : defaultdict(int)) akan dipanggil ketika Anda mencoba mengakses kunci yang tidak ada dan nilai pengembaliannya akan ditetapkan sebagai nilai baru dari kunci ini yang berarti dalam kasus kami nilai d[Key_dont_exist]akan defaultdict(int), dan jika Anda mencoba mengakses kunci dari defaultdict terakhir ini yaitu d[Key_dont_exist][Key_dont_exist]akan mengembalikan 0 yang merupakan nilai pengembalian argumen terakhir defaultdictyaitu int(), Semoga ini bermanfaat.
mouad

25
Argumen untuk defaultdictmenjadi fungsi. defaultdict(int)adalah kamus, sedangkan lambda: defaultdict(int)fungsi yang mengembalikan kamus.
has2k1

27
@ has2k1 Itu tidak benar. Argumen untuk defaultdict harus dapat dipanggil. Lambda adalah callable.
Niels Bom

2
@ RickyLevi, jika Anda ingin itu berfungsi, Anda bisa mengatakan: defaultdict(lambda: defaultdict(lambda: defaultdict(int)))
darophi

51

Parameter ke konstruktor defaultdict adalah fungsi yang akan dipanggil untuk membangun elemen baru. Jadi mari kita gunakan lambda!

>>> from collections import defaultdict
>>> d = defaultdict(lambda : defaultdict(int))
>>> print d[0]
defaultdict(<type 'int'>, {})
>>> print d[0]["x"]
0

Sejak Python 2.7, ada solusi yang lebih baik lagi menggunakan Counter :

>>> from collections import Counter
>>> c = Counter()
>>> c["goodbye"]+=1
>>> c["and thank you"]=42
>>> c["for the fish"]-=5
>>> c
Counter({'and thank you': 42, 'goodbye': 1, 'for the fish': -5})

Beberapa fitur bonus

>>> c.most_common()[:2]
[('and thank you', 42), ('goodbye', 1)]

Untuk informasi lebih lanjut lihat PyMOTW - Koleksi - tipe data wadah dan Dokumentasi Python - koleksi


5
Hanya untuk melengkapi lingkaran di sini, Anda ingin menggunakan d = defaultdict(lambda : Counter())daripada d = defaultdict(lambda : defaultdict(int))untuk secara khusus mengatasi masalah seperti yang diajukan sebelumnya.
gtion

3
@gtion Anda hanya bisa menggunakan d = defaultdict(Counter())tidak perlu untuk lambda dalam kasus ini
Deb

3
@ Deb Anda memiliki sedikit kesalahan - menghapus tanda kurung bagian dalam sehingga Anda melewatkan callable, bukan Counterobjek. Itu adalah:d = defaultdict(Counter)
Dillon Davis

29

Saya merasa sedikit lebih elegan untuk digunakan partial:

import functools
dd_int = functools.partial(defaultdict, int)
defaultdict(dd_int)

Tentu saja, ini sama dengan lambda.


1
Parsial juga lebih baik daripada lambda di sini karena dapat diterapkan secara rekursif :) lihat jawaban saya di bawah ini untuk metode pabrik defaultdict bersarang generik.
Campi

@Ampampi Anda tidak perlu parsial untuk aplikasi rekursif, AFAICT
Clément

10

Untuk referensi, dimungkinkan untuk menerapkan defaultdictmetode pabrik bersarang generik melalui:

from collections import defaultdict
from functools import partial
from itertools import repeat


def nested_defaultdict(default_factory, depth=1):
    result = partial(defaultdict, default_factory)
    for _ in repeat(None, depth - 1):
        result = partial(defaultdict, result)
    return result()

Kedalaman menentukan jumlah kamus bersarang sebelum jenis yang didefinisikan default_factorydigunakan. Sebagai contoh:

my_dict = nested_defaultdict(list, 3)
my_dict['a']['b']['c'].append('e')

Bisakah Anda memberikan contoh penggunaan? Tidak bekerja seperti yang saya harapkan. ndd = nested_defaultdict(dict) .... ndd['a']['b']['c']['d'] = 'e'throwsKeyError: 'b'
David Marx

Hai David, Anda perlu menentukan kedalaman kamus Anda, dalam contoh Anda 3 (ketika Anda mendefinisikan default_factory menjadi kamus juga. Nested_defaultdict (dict, 3) akan bekerja untuk Anda.
Campi

Ini sangat membantu, terima kasih! Satu hal yang saya perhatikan adalah bahwa ini menciptakan default_dict di depth=0, yang mungkin tidak selalu diinginkan jika kedalamannya tidak diketahui pada saat menelepon. Mudah diperbaiki dengan menambahkan garis if not depth: return default_factory(), di bagian atas fungsi, meskipun mungkin ada solusi yang lebih elegan.
Brendan

9

Jawaban sebelumnya telah membahas cara membuat dua level atau n-level defaultdict. Dalam beberapa kasus Anda ingin yang tak terbatas:

def ddict():
    return defaultdict(ddict)

Pemakaian:

>>> d = ddict()
>>> d[1]['a'][True] = 0.5
>>> d[1]['b'] = 3
>>> import pprint; pprint.pprint(d)
defaultdict(<function ddict at 0x7fcac68bf048>,
            {1: defaultdict(<function ddict at 0x7fcac68bf048>,
                            {'a': defaultdict(<function ddict at 0x7fcac68bf048>,
                                              {True: 0.5}),
                             'b': 3})})

1
Aku suka ini. Ini sangat sederhana, tetapi sangat berguna. Terima kasih!
rosstex

6

Orang lain telah menjawab dengan benar pertanyaan Anda tentang cara membuat yang berikut ini berfungsi:

for x in stuff:
    d[x.a][x.b] += x.c_int

Alternatifnya adalah menggunakan tuple untuk kunci:

d = defaultdict(int)
for x in stuff:
    d[x.a,x.b] += x.c_int
    # ^^^^^^^ tuple key

Hal yang menyenangkan tentang pendekatan ini adalah sederhana dan dapat dengan mudah diperluas. Jika Anda membutuhkan pemetaan tiga level, gunakan tuple tiga item untuk kunci tersebut.


4
Solusi ini berarti tidak mudah untuk mendapatkan semua d [xa], karena Anda perlu mengintrospeksi setiap kunci untuk melihat apakah xa sebagai elemen pertama dari tuple.
Matthew Schinckel

5
Jika Anda ingin bersarang 3 level, maka hanya mendefinisikannya sebagai 3 level: d = defaultdict (lambda: defaultdict (lambda: defaultdict (int)))
Matthew Schinckel
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.