Apa cara yang benar dan bagus untuk diterapkan __hash__()
?
Saya berbicara tentang fungsi yang mengembalikan kode hash yang kemudian digunakan untuk menyisipkan objek ke dalam tagar alias kamus.
Ketika __hash__()
mengembalikan integer dan digunakan untuk "binning" objek ke dalam hashtable, saya berasumsi bahwa nilai integer yang dikembalikan harus terdistribusi secara seragam untuk data umum (untuk meminimalkan benturan). Apa praktik yang baik untuk mendapatkan nilai seperti itu? Apakah tabrakan merupakan masalah? Dalam kasus saya, saya memiliki kelas kecil yang bertindak sebagai kelas wadah memegang beberapa int, beberapa mengapung dan tali.
sumber
__key
fungsi, ini tentang secepat hash apa pun. Tentu saja, jika atributnya dikenal sebagai bilangan bulat, dan jumlahnya tidak terlalu banyak, saya kira Anda bisa berjalan sedikit lebih cepat dengan beberapa hash buatan rumah, tetapi kemungkinan tidak akan terdistribusi dengan baik.hash((self.attr_a, self.attr_b, self.attr_c))
akan menjadi sangat cepat (dan benar ), karena pembuatantuple
s kecil dioptimalkan secara khusus, dan mendorong pekerjaan mendapatkan dan menggabungkan hash ke C builtin, yang biasanya lebih cepat daripada kode level Python.None
setelah perubahan kunci. Cara saya menyelesaikannya adalah dengan menyimpan id objek sebagai kunci, bukan hanya objek.John Millikin mengusulkan solusi yang mirip dengan ini:
Masalah dengan solusi ini adalah bahwa
hash(A(a, b, c)) == hash((a, b, c))
. Dengan kata lain, hash bertabrakan dengan hasrat anggota kunci. Mungkin ini tidak terlalu penting dalam praktek?Perbarui: dokumen Python sekarang merekomendasikan untuk menggunakan tuple seperti pada contoh di atas. Perhatikan bahwa dokumentasi menyatakan
Perhatikan bahwa yang terjadi adalah sebaliknya. Objek yang tidak membandingkan sama mungkin memiliki nilai hash yang sama. Tabrakan hash seperti itu tidak akan menyebabkan satu objek untuk menggantikan yang lain ketika digunakan sebagai kunci dict atau mengatur elemen selama objek tidak juga sama .
Solusi usang / buruk
The dokumentasi Python pada, yang memberi kami ini:__hash__
menyarankan untuk menggabungkan hash dari sub-komponen menggunakan sesuatu seperti XORPembaruan: seperti yang ditunjukkan Blckknght, mengubah urutan a, b, dan c dapat menyebabkan masalah. Saya menambahkan tambahan
^ hash((self._a, self._b, self._c))
untuk menangkap urutan nilai yang di-hash. Final ini^ hash(...)
dapat dihapus jika nilai-nilai yang digabungkan tidak dapat disusun ulang (misalnya, jika mereka memiliki tipe yang berbeda dan karena itu nilai_a
tidak akan pernah ditugaskan_b
atau_c
, dll.).sumber
hash(A(1, 2, 3))
akan sama denganhash(A(3, 1, 2))
(dan keduanya akan sama hash denganA
contoh lain dengan permutasi1
,2
dan3
sebagai nilainya). Jika Anda ingin menghindari instance Anda yang memiliki hash yang sama dengan tuple argumen mereka, cukup buat nilai sentinel (sebagai variabel kelas, atau sebagai global) lalu masukkan dalam tuple yang akan di-hash: return hash ((_ sentinel , self._a, self._b, self._c))isinstance
bisa menjadi masalah, karena objek dari subkelastype(self)
sekarang bisa sama dengan objektype(self)
. Jadi, Anda mungkin menemukan bahwa menambahkan aCar
dan aFord
keset()
dapat menghasilkan hanya satu objek yang disisipkan, tergantung pada urutan penyisipan. Selain itu, Anda mungkin mengalami situasi di manaa == b
Benar tetapib == a
Salah.B
, Anda mungkin ingin mengubahnya keisinstance(othr, B)
hash((type(self), self._a, self._b, self._c))
.B
bukannyatype(self)
, itu juga sering dianggap praktik yang lebih baik untuk kembaliNotImplemented
ketika menemukan jenis yang tak terduga di__eq__
bukannyaFalse
. Itu memungkinkan tipe yang ditentukan pengguna lain untuk mengimplementasikan__eq__
yang tahu tentangB
dan dapat membandingkannya dengan itu, jika mereka mau.Paul Larson dari Microsoft Research mempelajari berbagai fungsi hash. Dia memberitahuku itu
bekerja sangat baik untuk berbagai string. Saya telah menemukan bahwa teknik polinomial serupa bekerja dengan baik untuk menghitung hash dari subfield yang berbeda.
sumber
(hash * 1000003) XOR ord(c)
untuk string dengan perkalian sampul 32-bit. [Kutipan ]__hash__
metode; kita tidak perlu menggulung sendiri. Pertanyaannya adalah bagaimana menerapkan__hash__
untuk kelas tipikal yang ditentukan pengguna (dengan sekelompok properti menunjuk ke tipe bawaan atau mungkin ke kelas lain yang ditentukan pengguna semacam itu), yang tidak dijawab oleh jawaban ini sama sekali.Saya dapat mencoba menjawab bagian kedua dari pertanyaan Anda.
Tabrakan mungkin akan menghasilkan bukan dari kode hash itu sendiri, tetapi dari memetakan kode hash ke indeks dalam koleksi. Jadi misalnya fungsi hash Anda dapat mengembalikan nilai acak dari 1 hingga 10.000, tetapi jika tabel hash Anda hanya memiliki 32 entri, Anda akan mendapatkan tabrakan pada penyisipan.
Selain itu, saya akan berpikir bahwa tabrakan akan diselesaikan oleh koleksi secara internal, dan ada banyak metode untuk menyelesaikan tabrakan. Yang paling sederhana (dan terburuk) adalah, diberi entri untuk disisipkan di indeks i, tambahkan 1 ke i sampai Anda menemukan tempat kosong dan masukkan di sana. Pengambilan kemudian bekerja dengan cara yang sama. Ini menghasilkan pengambilan yang tidak efisien untuk beberapa entri, karena Anda dapat memiliki entri yang memerlukan melintasi seluruh koleksi untuk menemukan!
Metode resolusi tabrakan lainnya mengurangi waktu pengambilan dengan memindahkan entri dalam tabel hash ketika item dimasukkan untuk menyebarkan hal-hal. Ini meningkatkan waktu penyisipan tetapi mengasumsikan Anda membaca lebih dari yang Anda masukkan. Ada juga metode yang mencoba dan bercabang entri bertabrakan yang berbeda sehingga entri ke cluster di satu tempat tertentu.
Juga, jika Anda perlu mengubah ukuran koleksi, Anda harus mengulangi semuanya atau menggunakan metode hashing dinamis.
Singkatnya, tergantung pada apa yang Anda gunakan kode hash untuk Anda mungkin harus menerapkan metode resolusi tabrakan Anda sendiri. Jika Anda tidak menyimpannya dalam koleksi, Anda mungkin bisa pergi dengan fungsi hash yang hanya menghasilkan kode hash dalam rentang yang sangat besar. Jika demikian, Anda dapat memastikan wadah Anda lebih besar dari yang seharusnya (semakin besar semakin baik tentu saja) tergantung pada masalah memori Anda.
Berikut ini beberapa tautan jika Anda lebih tertarik:
penggabungan hashing di wikipedia
Wikipedia juga memiliki ringkasan berbagai metode resolusi tabrakan:
Juga, " Organisasi File dan Pemrosesan " oleh Tharp mencakup banyak metode resolusi tabrakan secara luas. IMO itu referensi yang bagus untuk algoritma hashing.
sumber
Penjelasan yang sangat bagus tentang kapan dan bagaimana mengimplementasikan
__hash__
fungsi ini di situs web programiz :Hanya tangkapan layar untuk memberikan ikhtisar: (Diperoleh 2019-12-13)
Adapun implementasi metode ini secara pribadi, situs yang disebutkan di atas memberikan contoh yang cocok dengan jawaban millerdev .
sumber
Bergantung pada ukuran nilai hash yang Anda kembalikan. Ini logika sederhana bahwa jika Anda perlu mengembalikan int 32bit berdasarkan hash dari empat int 32bit, Anda akan mendapatkan tabrakan.
Saya lebih suka operasi bit. Seperti, kode pseudo C berikut:
Sistem seperti itu bisa bekerja untuk float juga, jika Anda hanya menganggapnya sebagai nilai bitnya daripada benar-benar mewakili nilai floating-point, mungkin lebih baik.
Untuk string, saya punya sedikit / tidak tahu.
sumber