Bagaimana cara kerja collections.defaultdict?

532

Saya telah membaca contoh-contoh dalam python docs, tetapi masih tidak tahu apa arti metode ini. Adakah yang bisa membantu? Berikut adalah dua contoh dari python docs

>>> from collections import defaultdict

>>> s = 'mississippi'
>>> d = defaultdict(int)
>>> for k in s:
...     d[k] += 1
...
>>> d.items()
[('i', 4), ('p', 2), ('s', 4), ('m', 1)]

dan

>>> s = [('yellow', 1), ('blue', 2), ('yellow', 3), ('blue', 4), ('red', 1)]
>>> d = defaultdict(list)
>>> for k, v in s:
...     d[k].append(v)
...
>>> d.items()
[('blue', [2, 4]), ('red', [1]), ('yellow', [1, 3])]

parameter intdan listuntuk apa?

Lanston
sumber
15
BTW, tergantung pada use case Anda, jangan lupa untuk membekukan defaultdict untuk penggunaan read-only dengan mengatur default_factory = Nonesetelah Anda selesai mengisi defaultdict tersebut. Lihat pertanyaan ini .
Acumenus

Jawaban:

598

Biasanya, kamus Python melempar KeyErrorjika Anda mencoba untuk mendapatkan item dengan kunci yang saat ini tidak ada dalam kamus. The defaultdictkontras hanya akan menciptakan setiap item yang Anda mencoba untuk mengakses (yang tentu saja mereka tidak ada). Untuk membuat item "default" tersebut, ia memanggil objek fungsi yang Anda berikan ke konstruktor (lebih tepatnya, itu adalah objek "callable" yang sewenang-wenang, yang mencakup objek fungsi dan ketik). Untuk contoh pertama, item default dibuat menggunakan int(), yang akan mengembalikan objek integer 0. Untuk contoh kedua, item default dibuat menggunakan list(), yang mengembalikan objek daftar kosong baru.

Sven Marnach
sumber
4
Apakah secara fungsional berbeda dari menggunakan d.get (key, default_val)?
Ambareesh
29
@ Ambareesh d.get(key, default)tidak akan pernah memodifikasi kamus Anda - itu hanya akan mengembalikan default dan membiarkan kamus tidak berubah. defaultdict, di sisi lain, akan memasukkan kunci ke kamus jika belum ada di sana. Ini perbedaan besar; lihat contoh-contoh dalam pertanyaan untuk memahami mengapa.
Sven Marnach
Bagaimana kita tahu apa nilai default untuk setiap jenis? 0 untuk int () dan [] untuk daftar () adalah intuitif, tetapi ada juga tipe yang lebih kompleks atau mandiri.
Sean
1
@Sean defaultdictmemanggil konstruktor apa pun yang Anda lewati. Jika Anda memasukkan tipe T, nilai akan dibangun menggunakan T(). Tidak semua jenis dapat dibangun tanpa melewati parameter apa pun. Jika Anda ingin membuat tipe seperti itu, Anda memerlukan fungsi wrapper, atau semacamnya functools.partial(T, arg1, arg2).
Sven Marnach
224

defaultdictberarti bahwa jika kunci tidak ditemukan dalam kamus, maka alih-alih KeyErrordilempar, entri baru dibuat. Jenis entri baru ini diberikan oleh argumen defaultdict.

Sebagai contoh:

somedict = {}
print(somedict[3]) # KeyError

someddict = defaultdict(int)
print(someddict[3]) # print int(), thus 0
orlp
sumber
10
"Jenis pasangan baru ini diberikan oleh argumen defaultdict." Perhatikan bahwa argumen dapat setiap objek callable - bukan hanya mengetik fungsi. Sebagai contoh jika foo adalah fungsi yang mengembalikan "bar", foo dapat digunakan sebagai argumen ke default dict dan jika kunci yang tidak ada diakses, nilainya akan diatur ke "bar".
lf215
13
Atau jika Anda hanya ingin mengembalikan "bar": somedict = defaultdict (lambda: "bar")
Michael Scott Cuthbert
Baris keempat mengembalikan 0integer, jika someddict = defaultdict(list)itu mengembalikannya [ ]. Apakah 0 bilangan bulat default? Atau [] daftar default?
Gathide
Tidak juga. 0tidak dapat diubah - dalam CPython semua nilai dari -5hingga di 256-cache lajang tetapi ini adalah perilaku khusus-implementasi - dalam kedua kasus instance baru "dibuat" setiap kali dengan int()atau list(). Dengan begitu, d[k].append(v)dapat bekerja tanpa mengisi kamus dengan referensi ke daftar yang sama, yang akan membuat defaultdicthampir tidak berguna. Jika ini adalah perilaku, defaultdictakan mengambil nilai, bukan lambda, sebagai parameter. (Maaf untuk penjelasan yang mengerikan!)
wizzwizz4
93

defaultdict

"Kamus standar mencakup metode setdefault () untuk mengambil nilai dan menetapkan default jika nilai tidak ada. Sebaliknya, defaultdictmemungkinkan penelepon menentukan default (nilai yang akan dikembalikan) di depan ketika wadah diinisialisasi."

seperti yang didefinisikan oleh Doug Hellmann di The Python Standard Library by Example

Cara menggunakan defaultdict

Impor defaultdict

>>> from collections import defaultdict

Inisialisasi defaultdict

Inisialisasi dengan melewati

Callable sebagai argumen pertama (wajib)

>>> d_int = defaultdict(int)
>>> d_list = defaultdict(list)
>>> def foo():
...     return 'default value'
... 
>>> d_foo = defaultdict(foo)
>>> d_int
defaultdict(<type 'int'>, {})
>>> d_list
defaultdict(<type 'list'>, {})
>>> d_foo
defaultdict(<function foo at 0x7f34a0a69578>, {})

** kwargs sebagai argumen kedua (opsional)

>>> d_int = defaultdict(int, a=10, b=12, c=13)
>>> d_int
defaultdict(<type 'int'>, {'a': 10, 'c': 13, 'b': 12})

atau

>>> kwargs = {'a':10,'b':12,'c':13}
>>> d_int = defaultdict(int, **kwargs)
>>> d_int
defaultdict(<type 'int'>, {'a': 10, 'c': 13, 'b': 12})

Bagaimana cara kerjanya

Seperti kamus anak standar, kamus ini dapat melakukan semua fungsi yang sama.

Tetapi jika melewati kunci yang tidak dikenal itu mengembalikan nilai default bukannya kesalahan. Misalnya:

>>> d_int['a']
10
>>> d_int['d']
0
>>> d_int
defaultdict(<type 'int'>, {'a': 10, 'c': 13, 'b': 12, 'd': 0})

Jika Anda ingin mengubah nilai default, timpa default_factory:

>>> d_int.default_factory = lambda: 1
>>> d_int['e']
1
>>> d_int
defaultdict(<function <lambda> at 0x7f34a0a91578>, {'a': 10, 'c': 13, 'b': 12, 'e': 1, 'd': 0})

atau

>>> def foo():
...     return 2
>>> d_int.default_factory = foo
>>> d_int['f']
2
>>> d_int
defaultdict(<function foo at 0x7f34a0a0a140>, {'a': 10, 'c': 13, 'b': 12, 'e': 1, 'd': 0, 'f': 2})

Contoh dalam Pertanyaan

Contoh 1

Karena int telah diteruskan sebagai default_factory, kunci apa pun yang tidak dikenal akan mengembalikan 0 secara default.

Sekarang ketika string dilewatkan dalam loop, itu akan meningkatkan jumlah huruf-huruf itu di d.

>>> s = 'mississippi'
>>> d = defaultdict(int)
>>> d.default_factory
<type 'int'>
>>> for k in s:
...     d[k] += 1
>>> d.items()
[('i', 4), ('p', 2), ('s', 4), ('m', 1)]
>>> d
defaultdict(<type 'int'>, {'i': 4, 'p': 2, 's': 4, 'm': 1})

Contoh 2

Karena daftar telah diteruskan sebagai default_factory, kunci apa pun yang tidak dikenal (tidak ada) akan mengembalikan [] (mis. Daftar) secara default.

Sekarang karena daftar tuple dilewatkan dalam loop, itu akan menambahkan nilai dalam d [warna]

>>> s = [('yellow', 1), ('blue', 2), ('yellow', 3), ('blue', 4), ('red', 1)]
>>> d = defaultdict(list)
>>> d.default_factory
<type 'list'>
>>> for k, v in s:
...     d[k].append(v)
>>> d.items()
[('blue', [2, 4]), ('red', [1]), ('yellow', [1, 3])]
>>> d
defaultdict(<type 'list'>, {'blue': [2, 4], 'red': [1], 'yellow': [1, 3]})
Somendra Joshi
sumber
20

Kamus adalah cara mudah untuk menyimpan data untuk pengambilan nanti dengan nama (kunci). Kunci harus berupa benda unik dan tidak dapat diubah, dan biasanya berupa string. Nilai dalam kamus bisa berupa apa saja. Untuk banyak aplikasi, nilainya adalah tipe sederhana seperti bilangan bulat dan string.

Semakin menarik ketika nilai-nilai dalam kamus adalah koleksi (daftar, dikte, dll.) Dalam hal ini, nilai (daftar kosong atau dikt) harus diinisialisasi saat pertama kali kunci yang diberikan digunakan. Walaupun ini relatif mudah dilakukan secara manual, tipe defaultdict mengotomatiskan dan menyederhanakan jenis operasi ini. Defaultdict berfungsi persis seperti dict normal, tetapi diinisialisasi dengan fungsi ("pabrik default") yang tidak menggunakan argumen dan memberikan nilai default untuk kunci yang tidak ada.

Sebuah defaultdict tidak akan pernah meningkatkan KeyError. Kunci apa pun yang tidak ada mendapat nilai yang dikembalikan oleh pabrik default.

from collections import defaultdict
ice_cream = defaultdict(lambda: 'Vanilla')

ice_cream['Sarah'] = 'Chunky Monkey'
ice_cream['Abdul'] = 'Butter Pecan'

print(ice_cream['Sarah'])
>>>Chunky Monkey

print(ice_cream['Joe'])
>>>Vanilla

Berikut adalah contoh lain tentang Bagaimana menggunakan defaultdict, kita dapat mengurangi kompleksitas

from collections import defaultdict
# Time complexity O(n^2)
def delete_nth_naive(array, n):
    ans = []
    for num in array:
        if ans.count(num) < n:
            ans.append(num)
    return ans

# Time Complexity O(n), using hash tables.
def delete_nth(array,n):
    result = []
    counts = defaultdict(int)

    for i in array:
        if counts[i] < n:
            result.append(i)
            counts[i] += 1
    return result


x = [1,2,3,1,2,1,2,3]
print(delete_nth(x, n=2))
print(delete_nth_naive(x, n=2))

Kesimpulannya, setiap kali Anda membutuhkan kamus, dan nilai setiap elemen harus dimulai dengan nilai default, gunakan defaultdict.

dimensi
sumber
18

Ada penjelasan yang bagus tentang defaultdicts di sini: http://ludovf.net/blog/python-collections-defaultdict/

Pada dasarnya, parameter int dan daftar adalah fungsi yang Anda lewati. Ingat bahwa Python menerima nama fungsi sebagai argumen. int mengembalikan 0 secara default dan daftar mengembalikan daftar kosong ketika dipanggil dengan tanda kurung.

Dalam kamus normal, jika dalam contoh Anda saya mencoba menelepon d[a], saya akan mendapatkan kesalahan (KeyError), karena hanya kunci m, s, i dan p ada dan kunci a belum diinisialisasi. Tetapi dalam defaultdict, dibutuhkan nama fungsi sebagai argumen, ketika Anda mencoba menggunakan kunci yang belum diinisialisasi, itu hanya memanggil fungsi yang Anda berikan dan menetapkan nilai kembali sebagai nilai kunci baru.

varagrawal
sumber
7

Karena pertanyaannya adalah tentang "cara kerjanya", beberapa pembaca mungkin ingin melihat lebih banyak mur dan baut. Secara khusus, metode yang dimaksud adalah __missing__(key)metode. Lihat: https://docs.python.org/2/library/collections.html#defaultdict-objects .

Lebih konkret, jawaban ini menunjukkan cara memanfaatkan __missing__(key)dengan cara praktis: https://stackoverflow.com/a/17956989/1593924

Untuk mengklarifikasi apa arti 'callable', berikut adalah sesi interaktif (dari 2.7.6 tetapi juga bisa digunakan di v3):

>>> x = int
>>> x
<type 'int'>
>>> y = int(5)
>>> y
5
>>> z = x(5)
>>> z
5

>>> from collections import defaultdict
>>> dd = defaultdict(int)
>>> dd
defaultdict(<type 'int'>, {})
>>> dd = defaultdict(x)
>>> dd
defaultdict(<type 'int'>, {})
>>> dd['a']
0
>>> dd
defaultdict(<type 'int'>, {'a': 0})

Itu adalah penggunaan defaultdict yang paling umum (kecuali untuk penggunaan variabel x yang tidak berguna). Anda dapat melakukan hal yang sama dengan 0 sebagai nilai default eksplisit, tetapi tidak dengan nilai sederhana:

>>> dd2 = defaultdict(0)

Traceback (most recent call last):
  File "<pyshell#7>", line 1, in <module>
    dd2 = defaultdict(0)
TypeError: first argument must be callable

Alih-alih, yang berikut ini berfungsi karena lewat dalam fungsi sederhana (ia membuat fungsi tanpa nama yang membuat tanpa argumen dan selalu mengembalikan 0):

>>> dd2 = defaultdict(lambda: 0)
>>> dd2
defaultdict(<function <lambda> at 0x02C4C130>, {})
>>> dd2['a']
0
>>> dd2
defaultdict(<function <lambda> at 0x02C4C130>, {'a': 0})
>>> 

Dan dengan nilai default yang berbeda:

>>> dd3 = defaultdict(lambda: 1)
>>> dd3
defaultdict(<function <lambda> at 0x02C4C170>, {})
>>> dd3['a']
1
>>> dd3
defaultdict(<function <lambda> at 0x02C4C170>, {'a': 1})
>>> 
Jon Coombs
sumber
7

My 2 2 sendiri: Anda juga dapat subkelas defaultdict:

class MyDict(defaultdict):
    def __missing__(self, key):
        value = [None, None]
        self[key] = value
        return value

Ini bisa berguna untuk kasus yang sangat kompleks.

Edward Falk
sumber
4

Perilaku defaultdictdapat dengan mudah ditiru menggunakan dict.setdefaultalih-alih d[key]dalam setiap panggilan.

Dengan kata lain, kodenya:

from collections import defaultdict

d = defaultdict(list)

print(d['key'])                        # empty list []
d['key'].append(1)                     # adding constant 1 to the list
print(d['key'])                        # list containing the constant [1]

setara dengan:

d = dict()

print(d.setdefault('key', list()))     # empty list []
d.setdefault('key', list()).append(1)  # adding constant 1 to the list
print(d.setdefault('key', list()))     # list containing the constant [1]

Satu-satunya perbedaan adalah bahwa, menggunakan defaultdict, daftar konstruktor dipanggil hanya sekali, dan menggunakan dict.setdefaultdaftar konstruktor disebut lebih sering (tetapi kode dapat ditulis ulang untuk menghindari ini, jika benar-benar diperlukan).

Beberapa mungkin berpendapat ada pertimbangan kinerja, tetapi topik ini adalah ladang ranjau. Posting ini menunjukkan tidak ada keuntungan kinerja besar dalam menggunakan defaultdict, misalnya.

IMO, defaultdict adalah koleksi yang menambahkan lebih banyak kebingungan daripada manfaat kode. Tidak berguna bagi saya, tetapi orang lain mungkin berpikir berbeda.

Diego Queiroz
sumber
3

Alat defaultdict adalah wadah di kelas koleksi Python. Ini mirip dengan wadah kamus (dikt) biasa, tetapi memiliki satu perbedaan: Tipe data bidang nilai ditentukan saat inisialisasi.

Sebagai contoh:

from collections import defaultdict

d = defaultdict(list)

d['python'].append("awesome")

d['something-else'].append("not relevant")

d['python'].append("language")

for i in d.items():

    print i

Ini mencetak:

('python', ['awesome', 'language'])
('something-else', ['not relevant'])
saarthak johari
sumber
"Tipe data bidang nilai ditentukan saat inisialisasi": ini tidak benar. Fungsi pabrik elemen disediakan. Berikut listadalah fungsi untuk memanggil untuk mengisi nilai yang hilang, bukan jenis objek yang akan dibuat. Misalnya, untuk memiliki nilai default 1, Anda akan menggunakan lambda:1yang jelas bukan tipe.
asac
2

Saya pikir yang terbaik digunakan sebagai pengganti pernyataan kasus saklar. Bayangkan jika kita memiliki pernyataan kasus sakelar seperti di bawah ini:

option = 1

switch(option) {
    case 1: print '1st option'
    case 2: print '2nd option'
    case 3: print '3rd option'
    default: return 'No such option'
}

Tidak ada switchpernyataan kasus yang tersedia dalam python. Kita dapat mencapai hal yang sama dengan menggunakan defaultdict.

from collections import defaultdict

def default_value(): return "Default Value"
dd = defaultdict(default_value)

dd[1] = '1st option'
dd[2] = '2nd option'
dd[3] = '3rd option'

print(dd[4])    
print(dd[5])    
print(dd[3])

Mencetak:

Default Value
Default Value
3rd option

Dalam cuplikan di atas ddtidak memiliki tombol 4 atau 5 dan karenanya mencetak nilai default yang telah kami konfigurasikan dalam fungsi pembantu. Ini lebih bagus daripada kamus mentah di mana a KeyErrordilemparkan jika kunci tidak ada. Dari sini terbukti bahwa defaultdictlebih seperti pernyataan kasus saklar di mana kita dapat menghindari if-elif-elif-elseblok yang rumit .

Satu lagi contoh bagus yang sangat mengesankan saya dari situs ini adalah:

>>> from collections import defaultdict
>>> food_list = 'spam spam spam spam spam spam eggs spam'.split()
>>> food_count = defaultdict(int) # default value of int is 0
>>> for food in food_list:
...     food_count[food] += 1 # increment element's value by 1
...
defaultdict(<type 'int'>, {'eggs': 1, 'spam': 7})
>>>

Jika kami mencoba mengakses item apa pun selain eggsdan spamkami akan mendapatkan hitungan 0.

Swadhikar C
sumber
2

Tanpa defaultdict, Anda mungkin dapat menetapkan nilai baru untuk kunci yang tidak terlihat tetapi Anda tidak dapat memodifikasinya. Sebagai contoh:

import collections
d = collections.defaultdict(int)
for i in range(10):
  d[i] += i
print(d)
# Output: defaultdict(<class 'int'>, {0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 7, 8: 8, 9: 9})

import collections
d = {}
for i in range(10):
  d[i] += i
print(d)
# Output: Traceback (most recent call last): File "python", line 4, in <module> KeyError: 0
Ming Liu
sumber
2

Yah, defaultdict juga dapat meningkatkan keyerror dalam kasus berikut:

    from collections import defaultdict
    d = defaultdict()
    print(d[3]) #raises keyerror

Selalu ingat untuk memberikan argumen ke defaultdict seperti defaultdict (int).

Shweta Sharma
sumber
0

Kamus standar mencakup metode setdefault () untuk mengambil nilai dan menetapkan default jika nilai tidak ada. Sebaliknya, defaultdict memungkinkan penelepon menentukan default di depan ketika wadah diinisialisasi.

import collections

def default_factory():
    return 'default value'

d = collections.defaultdict(default_factory, foo='bar')
print 'd:', d
print 'foo =>', d['foo']
print 'bar =>', d['bar']

Ini berfungsi dengan baik selama semua kunci memiliki standar yang sama. Ini bisa sangat berguna jika default adalah tipe yang digunakan untuk mengumpulkan atau mengumpulkan nilai-nilai, seperti daftar, set, atau bahkan int. Dokumentasi perpustakaan standar mencakup beberapa contoh penggunaan defaultdict dengan cara ini.

$ python collections_defaultdict.py

d: defaultdict(<function default_factory at 0x100468c80>, {'foo': 'bar'})
foo => bar
bar => default value

sumber
0

Pendeknya:

defaultdict(int) - argumen int menunjukkan bahwa nilai-nilai akan menjadi tipe int.

defaultdict(list) - daftar argumen menunjukkan bahwa nilai akan menjadi tipe daftar.

Shravan kp
sumber