Mengapa menggunakan nomor utama dalam kode hash?


174

Saya hanya bertanya-tanya mengapa bilangan prima digunakan dalam metode kelas hashCode()? Misalnya, ketika menggunakan Eclipse untuk menghasilkan hashCode()metode saya selalu ada bilangan prima yang 31digunakan:

public int hashCode() {
     final int prime = 31;
     //...
}

Referensi:

Berikut ini adalah primer yang bagus pada Hashcode dan artikel tentang bagaimana hashing bekerja yang saya temukan (C # tetapi konsepnya dapat ditransfer): Pedoman dan aturan Eric Lippert untuk GetHashCode ()



Ini lebih atau kurang merupakan duplikat dari pertanyaan stackoverflow.com/questions/1145217/… .
Hans-Peter Störr

1
Silakan periksa jawaban saya di stackoverflow.com/questions/1145217/... Ini terkait dengan properti polinomial pada bidang (bukan cincin!), Maka dari itu bilangan prima.
TT_

Jawaban:


104

Karena Anda ingin jumlah yang Anda kalikan dengan dan jumlah ember yang Anda masukkan untuk memiliki faktorisasi prima ortogonal.

Misalkan ada 8 ember untuk dimasukkan. Jika nomor yang Anda gunakan untuk mengalikan dengan adalah kelipatan 8, maka ember yang dimasukkan hanya akan ditentukan oleh entri yang paling tidak signifikan (yang tidak dikalikan sama sekali). Entri yang serupa akan bertabrakan. Tidak bagus untuk fungsi hash.

31 adalah bilangan prima yang cukup besar sehingga jumlah ember tidak mungkin dapat dibagi olehnya (dan pada kenyataannya, implementasi HashMap java modern menjaga jumlah ember pada kekuatan 2).


9
Kemudian fungsi hash yang dikalikan 31 akan berkinerja tidak optimal. Namun, saya akan menganggap implementasi tabel hash seperti itu dirancang dengan buruk, mengingat betapa umum 31 sebagai pengganda.
ILMTitan

11
Jadi 31 dipilih berdasarkan asumsi bahwa implementor tabel hash tahu bahwa 31 umum digunakan dalam kode hash?
Steve Kuo

3
31 dipilih berdasarkan gagasan bahwa sebagian besar implementasi memiliki faktorisasi bilangan prima yang relatif kecil. Biasanya 2s, 3s, dan 5s. Ini mungkin mulai dari 10 dan tumbuh 3X ketika terlalu penuh. Ukurannya jarang sepenuhnya acak. Dan bahkan jika itu, 30/31 bukanlah peluang buruk untuk memiliki algoritma hash yang baik disinkronkan. Mungkin juga mudah untuk menghitung seperti yang dinyatakan orang lain.
ILMTitan

8
Dengan kata lain ... kita perlu mengetahui sesuatu tentang himpunan nilai input dan keteraturan himpunan, untuk menulis fungsi yang dirancang untuk menghapusnya dari keteraturan tersebut, sehingga nilai-nilai dalam himpunan tidak bertabrakan dengan yang sama. ember hash. Mengalikan / Membagi / Moduloing dengan bilangan prima mencapai yang mempengaruhi, karena jika Anda memiliki LOOP dengan item-X dan Anda melompat spasi-Y di loop, maka Anda tidak akan pernah kembali ke tempat yang sama sampai X menjadi faktor Y Karena X sering bilangan genap atau kekuatan 2, maka Anda perlu Y untuk menjadi prima sehingga X + X + X ... bukan merupakan faktor Y, jadi 31 yay! : /
Triynko

3
@ FrankQ. Ini adalah sifat aritmatika modular. (x*8 + y) % 8 = (x*8) % 8 + y % 8 = 0 + y % 8 = y % 8
ILMTitan

136

Nomor prima dipilih untuk mendistribusikan data terbaik di antara ember hash. Jika distribusi input acak dan tersebar merata, maka pilihan kode hash / modulus tidak masalah. Itu hanya berdampak ketika ada pola tertentu pada input.

Ini sering terjadi ketika berhadapan dengan lokasi memori. Misalnya, semua bilangan bulat 32-bit disejajarkan dengan alamat yang dapat dibagi oleh 4. Periksa tabel di bawah ini untuk memvisualisasikan efek menggunakan modulus prime vs non-prime:

Input       Modulo 8    Modulo 7
0           0           0
4           4           4
8           0           1
12          4           5
16          0           2
20          4           6
24          0           3
28          4           0

Perhatikan distribusi yang hampir sempurna ketika menggunakan modulus prima vs modulus non-prima.

Namun, meskipun contoh di atas sebagian besar dibuat-buat, prinsip umumnya adalah bahwa ketika berhadapan dengan pola input , menggunakan modulus bilangan prima akan menghasilkan distribusi terbaik.


17
Bukankah kita berbicara tentang pengali yang digunakan untuk menghasilkan kode hash, bukan modulo yang digunakan untuk mengurutkan kode-kode hash ke dalam ember?
ILMTitan

3
Prinsip yang sama. Dalam hal I / O, hash dimasukkan ke dalam operasi modulo tabel hash. Saya pikir intinya adalah bahwa jika Anda mengalikan dengan bilangan prima, Anda akan mendapatkan lebih banyak input yang didistribusikan secara acak ke titik di mana modulo bahkan tidak masalah. Karena fungsi hash mengambil kelonggaran dalam mendistribusikan input dengan lebih baik, membuatnya kurang teratur, mereka cenderung bertabrakan, terlepas dari modulo yang digunakan untuk menempatkannya ke dalam ember.
Triynko

9
Jawaban semacam ini sangat berguna karena seperti mengajar seseorang cara memancing, bukan menangkapnya untuk mereka. Ini membantu orang melihat dan memahami prinsip dasar di balik penggunaan bilangan prima untuk hash ... yaitu untuk mendistribusikan input secara tidak teratur sehingga mereka jatuh secara seragam ke dalam ember setelah dimodul :)
Triynko

29

Untuk apa nilainya, Efektif Java 2nd Edition mengesampingkan masalah matematika dan hanya mengatakan bahwa alasan untuk memilih 31 adalah:

  • Karena ini adalah prime yang aneh, dan "tradisional" menggunakan bilangan prima
  • Ini juga satu kurang dari kekuatan dua, yang memungkinkan untuk optimasi bitwise

Ini kutipan lengkapnya, dari Butir 9: Selalu timpa hashCodeketika Anda menggantiequals :

Nilai 31 dipilih karena ini adalah prime yang aneh. Jika itu genap dan multiplikasi meluap, informasi akan hilang, karena penggandaan 2 sama dengan pergeseran. Keuntungan menggunakan prime kurang jelas, tetapi tradisional.

Properti 31 yang bagus adalah bahwa perkalian dapat diganti dengan shift ( §15.19 ) dan pengurangan untuk kinerja yang lebih baik:

 31 * i == (i << 5) - i

VM modern melakukan optimasi semacam ini secara otomatis.


Sementara resep dalam item ini menghasilkan fungsi hash yang cukup baik, itu tidak menghasilkan fungsi hash yang canggih, juga pustaka platform Java tidak menyediakan fungsi hash tersebut pada rilis 1.6. Menulis fungsi hash seperti itu adalah topik penelitian, sebaiknya diserahkan kepada ahli matematika dan ilmuwan komputer teoretis.

Mungkin rilis nanti dari platform akan menyediakan fungsi hash yang canggih untuk kelasnya dan metode utilitas untuk memungkinkan programmer rata-rata untuk membangun fungsi hash tersebut. Sementara itu, teknik yang dijelaskan dalam item ini harus memadai untuk sebagian besar aplikasi.

Agak disederhanakan, dapat dikatakan bahwa menggunakan pengganda dengan banyak pembagi akan menghasilkan lebih banyak tabrakan hash . Karena untuk hashing yang efektif kami ingin meminimalkan jumlah tabrakan, kami mencoba menggunakan pengganda yang memiliki lebih sedikit pembagi. Bilangan prima menurut definisi memiliki dua pembagi positif yang berbeda.

Pertanyaan-pertanyaan Terkait


4
Eh, tapi di sana sudah banyak yang cocok bilangan prima yang baik 2 ^ n + 1 (disebut bilangan prima Fermat ), yaitu 3, 5, 17, 257, 65537atau 2 ^ n - 1 ( Mersenne bilangan prima ): 3, 7, 31, 127, 8191, 131071, 524287, 2147483647. Namun 31(dan bukan, katakanlah, 127) diikutkan.
Dmitry Bychenko

4
"karena ini adalah prime yang aneh" ... hanya ada satu prime even: P
Martin Schneider

Saya tidak suka kata-katanya "kurang jelas, tetapi tradisional" dalam "Java Efektif". Jika dia tidak ingin masuk ke dalam rincian matematis, dia harus menulis sesuatu seperti "memiliki alasan matematika yang serupa" sebagai gantinya. Cara dia menulis sepertinya hanya memiliki latar belakang sejarah :(
Qw3ry

5

Saya mendengar bahwa 31 dipilih sehingga kompiler dapat mengoptimalkan penggandaan ke shift 5 bit kemudian kurangi nilainya.


bagaimana kompiler dapat mengoptimalkan cara itu? x * 31 == x * 32-1 tidak benar untuk semua x afterall. Yang Anda maksud adalah shift kiri 5 (sama dengan kalikan dengan 32) dan kemudian kurangi nilai aslinya (x dalam contoh saya). Meskipun ini mungkin lebih cepat daripada perkalian (mungkin bukan untuk prosesor cpu modern), ada faktor yang lebih penting untuk dipertimbangkan ketika memilih perkalian untuk kode hasch (distribusi nilai input yang sama ke bucket muncul dalam pikiran)
Grizzly

Lakukan sedikit pencarian, ini pendapat yang cukup umum.
Steve Kuo

4
Pendapat umum tidak relevan.
fraktur

1
@Grizzly, ini lebih cepat daripada perkalian. IMul ​​memiliki latensi minimum 3 siklus pada CPU modern apa pun. (lihat manual kabut kabut) mov reg1, reg2-shl reg1,5-sub reg1,reg2dapat dijalankan dalam 2 siklus. (Pindah hanya mengganti nama dan mengambil 0 siklus).
Johan

3

Berikut ini kutipan yang sedikit lebih dekat dengan sumbernya.

Intinya adalah:

  • 31 adalah prima, yang mengurangi tabrakan
  • 31 menghasilkan distribusi yang baik, dengan
  • tradeoff yang wajar dalam kecepatan

3

Pertama Anda menghitung nilai hash modulo 2 ^ 32 (ukuran an int), jadi Anda menginginkan sesuatu yang relatif prima ke 2 ^ 32 (relatif prima berarti tidak ada pembagi umum). Angka ganjil akan melakukan untuk itu.

Kemudian untuk tabel hash yang diberikan indeks biasanya dihitung dari nilai hash modulo ukuran tabel hash, jadi Anda menginginkan sesuatu yang relatif prima dengan ukuran tabel hash. Seringkali ukuran tabel hash dipilih sebagai bilangan prima karena alasan itu. Dalam kasus Java, implementasi Sun memastikan bahwa ukuran selalu merupakan kekuatan dua, sehingga angka ganjil juga cukup di sini. Ada juga beberapa pemijatan tambahan dari kunci hash untuk membatasi tabrakan lebih lanjut.

Efek buruk jika tabel hash dan pengali memiliki faktor yang sama nadalah bahwa dalam keadaan tertentu hanya 1 / n entri dalam tabel hash yang akan digunakan.


2

Alasan mengapa bilangan prima digunakan adalah untuk meminimalkan tabrakan ketika data menunjukkan beberapa pola tertentu.

Hal pertama yang pertama: Jika data acak maka tidak perlu untuk bilangan prima, Anda dapat melakukan operasi mod terhadap nomor apa pun dan Anda akan memiliki jumlah tabrakan yang sama untuk setiap kemungkinan nilai modulus.

Tetapi ketika data tidak acak maka hal-hal aneh terjadi. Misalnya pertimbangkan data numerik yang selalu kelipatan 10.

Jika kami menggunakan mod 4, kami menemukan:

10 mod 4 = 2

20 mod 4 = 0

30 mod 4 = 2

40 mod 4 = 0

50 mod 4 = 2

Jadi dari 3 kemungkinan nilai modulus (0,1,2,3) hanya 0 dan 2 yang akan bertabrakan, itu buruk.

Jika kita menggunakan bilangan prima seperti 7:

10 mod 7 = 3

20 mod 7 = 6

30 mod 7 = 2

40 mod 7 = 4

50 mod 7 = 1

dll

Kami juga mencatat bahwa 5 bukan pilihan yang baik tetapi 5 adalah prima alasannya adalah bahwa semua kunci kami adalah kelipatan 5. Ini berarti kita harus memilih bilangan prima yang tidak membagi kunci kami, memilih bilangan prima besar adalah biasanya cukup.

Jadi keliru di sisi berulang-ulang alasan bilangan prima digunakan adalah untuk menetralisir efek pola dalam kunci dalam distribusi tabrakan fungsi hash.


1

31 juga khusus untuk Java HashMap yang menggunakan tipe data hash int. Dengan demikian kapasitas maks 2 ^ 32. Tidak ada gunanya menggunakan Fermat yang lebih besar atau bilangan prima Mersenne.


0

Ini biasanya membantu mencapai penyebaran data Anda yang lebih merata di antara hash bucket, terutama untuk kunci dengan entropi rendah.

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.