Saya tidak mengerti bagaimana perulangan kamus atau diatur dengan python dilakukan dengan urutan 'sewenang-wenang'.
Maksud saya, ini adalah bahasa pemrograman sehingga segala sesuatu dalam bahasa tersebut harus ditentukan 100%, benar? Python harus memiliki beberapa jenis algoritma yang memutuskan bagian mana dari kamus atau set yang dipilih, 1, kedua dan seterusnya.
Apa yang saya lewatkan?
python
dictionary
set
python-internals
Edgar Aroutiounian
sumber
sumber
Jawaban:
Urutannya tidak sewenang-wenang, tetapi tergantung pada penyisipan dan penghapusan riwayat kamus atau set, serta pada implementasi Python tertentu. Untuk sisa jawaban ini, untuk 'kamus', Anda juga dapat membaca 'set'; set diimplementasikan sebagai kamus dengan kunci adil dan tanpa nilai.
Kunci hash, dan nilai hash ditugaskan ke slot di tabel dinamis (dapat tumbuh atau menyusut berdasarkan kebutuhan). Dan proses pemetaan itu dapat menyebabkan tabrakan, yang berarti bahwa sebuah kunci harus ditempatkan di slot berikutnya berdasarkan apa yang sudah ada.
Daftar loop konten di atas slot, dan kunci terdaftar dalam urutan saat ini berada di tabel.
Ambil kunci
'foo'
dan'bar'
, misalnya, dan mari kita asumsikan ukuran tabel adalah 8 slot. Dalam Python 2.7,hash('foo')
is-4177197833195190597
,hash('bar')
is327024216814240868
. Modulo 8, itu berarti dua tombol ini ditempatkan di slot 3 dan 4 lalu:Ini menginformasikan pesanan listing mereka:
Semua slot kecuali 3 dan 4 kosong, looping di atas meja pertama daftar slot 3, lalu slot 4, jadi
'foo'
terdaftar sebelumnya'bar'
.bar
danbaz
, bagaimanapun, memiliki nilai hash yang persis 8 terpisah dan dengan demikian peta ke slot yang sama persis,4
:Pesanan mereka sekarang tergantung pada kunci mana yang ditempatkan pertama kali; tombol kedua harus dipindahkan ke slot berikutnya:
Urutan tabel berbeda di sini, karena salah satu atau kunci lainnya ditempatkan terlebih dahulu.
Nama teknis untuk struktur dasar yang digunakan oleh CPython (implementasi Python yang paling umum digunakan) adalah tabel hash , yang menggunakan pengalamatan terbuka. Jika Anda penasaran, dan memahami C dengan cukup baik, lihat implementasi C untuk semua detail (terdokumentasi dengan baik). Anda juga dapat menonton presentasi Pycon 2010 ini oleh Brandon Rhodes tentang cara
dict
kerja CPython , atau mengambil salinan Kode Cantik , yang mencakup bab tentang implementasi yang ditulis oleh Andrew Kuchling.Perhatikan bahwa pada Python 3.3, benih hash acak juga digunakan, membuat tabrakan hash tidak dapat diprediksi untuk mencegah beberapa jenis penolakan layanan (di mana seorang penyerang membuat server Python tidak responsif dengan menyebabkan benturan hash massal). Ini berarti bahwa urutan kamus atau set yang diberikan kemudian juga tergantung pada seed hash acak untuk permintaan Python saat ini.
Implementasi lain bebas menggunakan struktur berbeda untuk kamus, asalkan memenuhi antarmuka Python yang terdokumentasi untuk mereka, tapi saya percaya bahwa semua implementasi sejauh ini menggunakan variasi dari tabel hash.
CPython 3.6 memperkenalkan baru
dict
implementasi yang mempertahankan urutan penyisipan, dan lebih cepat dan lebih efisien untuk mem-boot. Daripada menyimpan tabel jarang yang besar di mana setiap baris mereferensikan nilai hash yang tersimpan, dan objek kunci dan nilai, implementasi baru menambahkan array hash yang lebih kecil yang hanya mereferensikan indeks dalam tabel 'padat' yang terpisah (yang hanya berisi banyak baris karena ada pasangan nilai kunci yang sebenarnya), dan itu adalah tabel padat yang terjadi untuk mendaftar item yang ada dalam urutan. Lihat proposal ke Python-Dev untuk lebih jelasnya . Perhatikan bahwa dalam Python 3.6 ini dianggap sebagai detail implementasi, Python-the-language tidak menentukan bahwa implementasi lain harus mempertahankan ketertiban. Ini berubah dalam Python 3.7, di mana detail ini dinaikkan menjadi spesifikasi bahasa ; untuk implementasi apa pun agar kompatibel dengan Python 3.7 atau yang lebih baru, harus menyalin perilaku mempertahankan pesanan ini. Dan untuk menjadi eksplisit: perubahan ini tidak berlaku untuk set, karena set sudah memiliki struktur hash 'kecil'.Python 2.7 dan yang lebih baru juga menyediakan
OrderedDict
kelas , subkelasdict
yang menambahkan struktur data tambahan untuk merekam urutan kunci. Dengan harga beberapa kecepatan dan memori tambahan, kelas ini mengingat bagaimana Anda memasukkan kunci; daftar kunci, nilai, atau item kemudian akan melakukannya dalam urutan itu. Ini menggunakan daftar tertaut ganda yang disimpan dalam kamus tambahan untuk menjaga agar pesanan selalu diperbarui secara efisien. Lihat posting Raymond Hettinger yang menguraikan gagasan itu .OrderedDict
benda memiliki kelebihan lain, seperti dipesan ulang .Jika Anda menginginkan set yang dipesan, Anda dapat menginstal
oset
paket ; ini bekerja pada Python 2.5 dan lebih tinggi.sumber
__hash__
dan__eq__
(dan tidak ada yang lain) praktis jaminan bahasa, bukan detail implementasi.dictobject.c
) dan berakhir dengan perbandingan yang jauh lebih sedikit daripada yang perlu dilakukan oleh BTree bahkan untuk menemukan yang tepat. subtree.Ini lebih merupakan respons terhadap Python 3.41 Satu set sebelum ditutup sebagai duplikat.
Yang lain benar: jangan mengandalkan pesanan. Jangan pura-pura ada.
Yang mengatakan, ada satu hal yang dapat Anda andalkan:
Artinya, pesanannya stabil .
Memahami mengapa ada tatanan yang dirasakan membutuhkan pemahaman beberapa hal:
Python itu menggunakan hash set ,
Bagaimana set hash CPython disimpan dalam memori dan
Bagaimana angka di-hash
Dari atas:
Sebuah set hash adalah metode menyimpan data acak dengan waktu sangat cepat lookup.
Ini memiliki array dukungan:
Kami akan mengabaikan objek boneka khusus, yang ada hanya untuk membuat penghapusan lebih mudah untuk ditangani, karena kami tidak akan menghapus dari set ini.
Untuk mendapatkan pencarian yang sangat cepat, Anda melakukan sihir untuk menghitung hash dari suatu objek. Satu-satunya aturan adalah bahwa dua objek yang sama memiliki hash yang sama. (Tetapi jika dua objek memiliki hash yang sama, mereka bisa tidak sama.)
Anda kemudian membuat indeks dengan mengambil modulus oleh panjang array:
Ini membuatnya sangat cepat untuk mengakses elemen.
Hash hanya sebagian besar cerita, karena
hash(n) % len(storage)
danhash(m) % len(storage)
dapat menghasilkan jumlah yang sama. Dalam hal ini, beberapa strategi berbeda dapat mencoba dan menyelesaikan konflik. CPython menggunakan "linear probing" 9 kali sebelum melakukan hal-hal yang rumit, sehingga akan terlihat di sebelah kiri slot hingga 9 tempat sebelum mencari di tempat lain.Kumpulan hash CPython disimpan seperti ini:
Kumpulan hash tidak boleh lebih dari 2/3 penuh . Jika ada 20 elemen dan panjang array 30 elemen, toko dukungan akan diubah ukurannya menjadi lebih besar. Ini karena Anda lebih sering bertabrakan dengan toko dukungan kecil, dan tabrakan memperlambat semuanya.
Toko dukungan ukuran dalam kekuatan 4, mulai dari 8, kecuali untuk set besar (elemen 50k) yang mengubah ukuran dalam kekuatan dua: (8, 32, 128, ...).
Jadi ketika Anda membuat sebuah array, backing store adalah panjang 8. Ketika 5 penuh dan Anda menambahkan elemen, itu akan secara singkat berisi 6 elemen.
6 > ²⁄₃·8
jadi ini memicu pengubahan ukuran, dan backing store empat kali lipat ke ukuran 32.Akhirnya,
hash(n)
hanya mengembalikann
angka (kecuali-1
yang khusus).Jadi, mari kita lihat yang pertama:
len(v_set)
adalah 10, jadi backing store setidaknya 15 (+1) setelah semua item ditambahkan . Kekuatan yang relevan dari 2 adalah 32. Jadi toko dukungan adalah:Kita punya
jadi masukkan ini sebagai:
Jadi kami akan mengharapkan pesanan seperti
dengan 1 atau 33 yang tidak di mulai di tempat lain. Ini akan menggunakan linear probing, jadi kita akan memiliki:
atau
Anda mungkin berharap 33 menjadi salah satu yang dipindahkan karena 1 sudah ada di sana, tetapi karena ukuran yang terjadi saat set sedang dibangun, ini sebenarnya tidak terjadi. Setiap kali set dibangun kembali, item yang sudah ditambahkan akan disusun ulang secara efektif.
Sekarang Anda bisa melihat alasannya
mungkin dalam rangka. Ada 14 elemen, jadi backing store setidaknya 21 + 1, yang berarti 32:
1 hingga 13 hash di 13 slot pertama. 20 masuk dalam slot 20.
55 masuk dalam slot
hash(55) % 32
yaitu 23:Jika kami memilih 50 sebagai gantinya, kami harapkan
Dan lihatlah:
pop
diimplementasikan dengan cukup sederhana oleh hal-hal yang terlihat: itu melintasi daftar dan muncul yang pertama.Ini semua detail implementasi.
sumber
"Sewenang-wenang" tidak sama dengan "tidak ditentukan".
Apa yang mereka katakan adalah bahwa tidak ada properti yang berguna dari urutan iterasi kamus yang "di antarmuka publik". Hampir pasti ada banyak properti dari urutan iterasi yang sepenuhnya ditentukan oleh kode yang saat ini mengimplementasikan iterasi kamus, tetapi penulis tidak menjanjikannya kepada Anda sebagai sesuatu yang dapat Anda gunakan. Ini memberi mereka lebih banyak kebebasan untuk mengubah properti ini antara versi Python (atau bahkan hanya dalam kondisi operasi yang berbeda, atau sepenuhnya secara acak saat runtime) tanpa khawatir bahwa program Anda akan rusak.
Jadi, jika Anda menulis program yang bergantung pada properti apa pun di semua urutan kamus, maka Anda "melanggar kontrak" menggunakan jenis kamus, dan pengembang Python tidak menjanjikan bahwa ini akan selalu bekerja, bahkan jika itu tampaknya bekerja untuk saat ini ketika Anda mengujinya. Ini pada dasarnya sama dengan mengandalkan "perilaku tidak terdefinisi" dalam C.
sumber
d.items()
pada dasarnya identik denganzip(d.keys(), d.values())
. Jika ada item yang ditambahkan ke kamus, semua taruhan dimatikan. Pesanan dapat berubah sepenuhnya (jika tabel hash perlu diubah ukurannya), meskipun sebagian besar waktu Anda hanya akan menemukan item baru muncul di beberapa tempat sewenang-wenang dalam urutan.Jawaban lain untuk pertanyaan ini sangat bagus dan ditulis dengan baik. OP bertanya "bagaimana" yang saya artikan sebagai "bagaimana mereka lolos" atau "mengapa".
Dokumentasi Python mengatakan kamus tidak dipesan karena kamus Python mengimplementasikan array asosiatif tipe data abstrak . Seperti yang mereka katakan
Dengan kata lain, seorang siswa ilmu komputer tidak dapat berasumsi bahwa array asosiatif dipesan. Hal yang sama berlaku untuk set dalam matematika
dan ilmu komputer
Menerapkan kamus menggunakan tabel hash adalah detail implementasi yang menarik karena memiliki sifat yang sama dengan array asosiatif sejauh menyangkut urutan.
sumber
Python menggunakan tabel hash untuk menyimpan kamus, sehingga tidak ada urutan dalam kamus atau objek iterable lainnya yang menggunakan tabel hash.
Tetapi mengenai indeks item dalam objek hash, python menghitung indeks berdasarkan kode berikut dalam
hashtable.c
:Oleh karena itu, karena nilai hash bilangan bulat adalah bilangan bulat itu sendiri * indeks didasarkan pada angka (
ht->num_buckets - 1
adalah konstanta) sehingga indeks dihitung oleh Bitwise-dan di antara(ht->num_buckets - 1)
dan angka itu sendiri * (berharap untuk -1 yang hashnya adalah -2 ), dan untuk objek lain dengan nilai hash mereka.pertimbangkan contoh berikut dengan
set
menggunakan hash-table:Untuk nomor yang
33
kami miliki:Sebenarnya itu:
Catatan dalam hal ini
(ht->num_buckets - 1)
adalah8-1=7
atau0b111
.Dan untuk
1919
:Dan untuk
333
:Untuk detail lebih lanjut tentang fungsi hash python ada baiknya untuk membaca kutipan berikut dari kode sumber python :
* Fungsi hash untuk kelas
int
:sumber
Dimulai dengan Python 3.7 (dan sudah ada di CPython 3.6 ), item kamus tetap dalam urutan mereka dimasukkan .
sumber