Dahulu kala, saya membeli buku struktur data dari tabel tawar-menawar seharga $ 1,25. Di dalamnya, penjelasan untuk fungsi hashing mengatakan bahwa ia harus diubah oleh bilangan prima karena "sifat matematika".
Apa yang Anda harapkan dari buku seharga $ 1,25?
Lagi pula, saya sudah bertahun-tahun memikirkan sifat matematika, dan masih belum bisa mengetahuinya.
Apakah distribusi angka benar-benar lebih bahkan ketika ada jumlah utama ember? Atau ini adalah kisah seorang programmer lama bahwa semua orang menerima karena semua orang menerimanya?
language-agnostic
data-structures
hash
theschmitzer
sumber
sumber
Jawaban:
Biasanya fungsi hash sederhana bekerja dengan mengambil "bagian komponen" dari input (karakter dalam kasus string), dan mengalikannya dengan kekuatan beberapa konstanta, dan menambahkannya bersama dalam beberapa tipe integer. Jadi misalnya hash khas (walaupun tidak terlalu bagus) dari string mungkin:
Kemudian jika sekelompok string yang semuanya memiliki karakter pertama yang sama dimasukkan, maka hasilnya semua akan menjadi modulo k yang sama, setidaknya sampai tipe integer meluap.
[Sebagai contoh, kode Java string hash mirip dengan ini - ia melakukan urutan karakter terbalik, dengan k = 31. Jadi Anda mendapatkan hubungan yang mengejutkan modulo 31 antara string yang berakhir dengan cara yang sama, dan hubungan yang mengejutkan modulo 31 ^ antara string yang sama kecuali di dekat akhir. Ini tidak secara serius mengacaukan perilaku hashtable.]
Hash bekerja dengan mengambil modulus hash di atas jumlah bucket.
Sangat penting dalam hashtabel untuk tidak menghasilkan collision untuk kemungkinan kasus, karena collision mengurangi efisiensi hashtable.
Sekarang, anggaplah seseorang meletakkan sejumlah nilai ke dalam hashtable yang memiliki hubungan antara item-item tersebut, seperti semua yang memiliki karakter pertama yang sama. Ini adalah pola penggunaan yang cukup dapat diprediksi, saya katakan, jadi kami tidak ingin itu menghasilkan terlalu banyak tabrakan.
Ternyata "karena sifat matematika", jika konstanta yang digunakan dalam hash, dan jumlah ember, adalah koprime , maka tabrakan diminimalkan dalam beberapa kasus umum. Jika mereka bukan koprime, maka ada beberapa hubungan yang cukup sederhana antara input yang tabrakannya tidak diminimalkan. Semua hash keluar sama modulo faktor umum, yang berarti mereka semua akan jatuh ke 1 / n dari ember yang memiliki nilai modulo faktor umum. Anda mendapatkan n kali lebih banyak tabrakan, di mana n adalah faktor umum. Karena n setidaknya 2, saya akan mengatakan itu tidak dapat diterima untuk kasus penggunaan yang cukup sederhana untuk menghasilkan setidaknya dua kali lebih banyak tabrakan seperti biasa. Jika beberapa pengguna akan memecah distribusi kami menjadi ember, kami ingin itu menjadi kecelakaan aneh, bukan penggunaan yang dapat diprediksi sederhana.
Sekarang, implementasi hashtable jelas tidak memiliki kendali atas item yang dimasukkan ke dalamnya. Mereka tidak bisa mencegah mereka berhubungan. Jadi yang harus dilakukan adalah memastikan bahwa konstanta dan jumlah bucket adalah koprime. Dengan begitu Anda tidak mengandalkan komponen "terakhir" sendirian untuk menentukan modulus bucket sehubungan dengan beberapa faktor umum kecil. Sejauh yang saya tahu mereka tidak harus menjadi yang utama untuk mencapai ini, hanya coprime.
Tetapi jika fungsi hash dan hashtable ditulis secara independen, maka hashtable tidak tahu bagaimana fungsi hash bekerja. Mungkin menggunakan konstanta dengan faktor-faktor kecil. Jika Anda beruntung, itu mungkin bekerja dengan sangat berbeda dan tidak linier. Jika hash cukup baik, maka jumlah bucket apa pun baik-baik saja. Tetapi hashtable paranoid tidak dapat mengasumsikan fungsi hash yang baik, jadi sebaiknya gunakan jumlah bucket yang prima Demikian pula fungsi hash paranoid harus menggunakan konstanta prime yang lebih besar, untuk mengurangi kemungkinan seseorang menggunakan sejumlah ember yang kebetulan memiliki faktor umum dengan konstanta.
Dalam praktiknya, saya pikir cukup normal untuk menggunakan kekuatan 2 sebagai jumlah ember. Ini nyaman dan menghemat harus mencari sekitar atau pra-pilih nomor utama dari besaran yang tepat. Jadi, Anda mengandalkan fungsi hash untuk tidak menggunakan pengganda genap, yang umumnya merupakan asumsi yang aman. Tetapi Anda masih bisa mendapatkan perilaku hashing buruk sesekali berdasarkan fungsi hash seperti di atas, dan jumlah bucket prima dapat membantu lebih lanjut.
Menerapkan prinsip bahwa "semuanya harus prima" sejauh yang saya tahu cukup tetapi bukan syarat yang diperlukan untuk distribusi yang baik di atas tagar. Ini memungkinkan semua orang untuk beroperasi tanpa perlu berasumsi bahwa yang lain telah mengikuti aturan yang sama.
[Sunting: ada alasan lain yang lebih terspesialisasi untuk menggunakan jumlah bucket prima, yaitu jika Anda menangani tabrakan dengan probe linear. Lalu Anda menghitung langkah dari kode hash, dan jika langkah itu keluar menjadi faktor jumlah ember maka Anda hanya bisa melakukan (bucket_count / langkah) penyelidikan sebelum Anda kembali ke tempat Anda mulai. Kasus yang paling ingin Anda hindari adalah stride = 0, tentu saja, yang harus dikurung khusus, tetapi untuk menghindari juga casing-khusus bucket_count / stride sama dengan integer kecil, Anda bisa menjadikan bucket_count prima dan tidak peduli apa pun yang terjadi. langkahnya asalkan bukan 0.]
sumber
Hal pertama yang Anda lakukan ketika memasukkan / mengambil kembali dari tabel hash adalah menghitung kode hash untuk kunci yang diberikan dan kemudian menemukan ember yang benar dengan memotong kode hash ke ukuran hashTable dengan melakukan hashCode% table_length. Berikut adalah 2 'pernyataan' yang kemungkinan besar telah Anda baca di suatu tempat
Dan inilah buktinya.
Jika seandainya fungsi kode hash Anda menghasilkan kode hash berikut ini antara lain {x, 2x, 3x, 4x, 5x, 6x ...}, maka semua ini akan dikelompokkan dalam hanya sejumlah ember, di mana m = table_length / GreatestCommonFactor (table_length, x). (Sangat sepele untuk memverifikasi / menurunkan ini). Sekarang Anda dapat melakukan salah satu dari yang berikut untuk menghindari pengelompokan
Pastikan Anda tidak menghasilkan terlalu banyak kode hash yang merupakan kelipatan dari kode hash lain seperti dalam {x, 2x, 3x, 4x, 5x, 6x ...}. Tetapi ini mungkin agak sulit jika hashTable Anda seharusnya memiliki jutaan entri. Atau cukup buat m sama dengan table_length dengan membuat GreatestCommonFactor (table_length, x) sama dengan 1, yaitu dengan membuat table_length coprime dengan x. Dan jika x dapat berupa angka apa saja maka pastikan bahwa table_length adalah bilangan prima.
Dari - http://srinvis.blogspot.com/2006/07/hash-table-lengths-and-prime-numbers.html
sumber
http://computinglife.wordpress.com/2008/11/20/why-do-hash-functions-use-prime-numbers/
Penjelasan yang cukup jelas, dengan gambar juga.
Sunting: Sebagai ringkasan, bilangan prima digunakan karena Anda memiliki peluang terbaik untuk mendapatkan nilai unik ketika mengalikan nilai dengan bilangan prima yang dipilih dan menambahkan semuanya. Misalnya diberi string, mengalikan setiap nilai huruf dengan bilangan prima dan kemudian menambahkan semuanya akan memberi Anda nilai hash.
Pertanyaan yang lebih baik adalah, mengapa tepatnya angka 31?
sumber
*32
merupakan pergeseran bit sederhana, atau bahkan lebih baik faktor skala alamat langsung (misalnyalea eax,eax*8; leax, eax,eax*4
pada x86 / x64). Jadi*31
adalah kandidat yang baik untuk penggandaan bilangan prima. Ini cukup benar beberapa tahun yang lalu - sekarang arsitektur CPU terbaru memiliki perkalian yang hampir instan - pembagian selalu lebih lambat ...tl; dr
index[hash(input)%2]
akan menghasilkan tabrakan untuk setengah dari semua hash yang mungkin dan berbagai nilai.index[hash(input)%prime]
menghasilkan tabrakan <2 dari semua hash yang mungkin. Memperbaiki pembagi ukuran tabel juga memastikan bahwa jumlahnya tidak boleh lebih besar dari tabel.sumber
Primes digunakan karena Anda memiliki peluang bagus untuk mendapatkan nilai unik untuk fungsi-hash tipikal yang menggunakan polinomial modulo P. Katakanlah, Anda menggunakan fungsi hash tersebut untuk string dengan panjang <<N, dan Anda memiliki tabrakan. Itu berarti bahwa 2 polinomial yang berbeda menghasilkan nilai modulo P. yang sama. Perbedaan polinomial tersebut adalah polinomial dengan derajat N yang sama (atau kurang). Tidak lebih dari N root (di sinilah sifat matematika menunjukkan dirinya, karena klaim ini hanya berlaku untuk polinomial atas bidang => bilangan prima). Jadi, jika N jauh lebih kecil dari P, Anda kemungkinan tidak akan mengalami tabrakan. Setelah itu, percobaan mungkin dapat menunjukkan bahwa 37 cukup besar untuk menghindari tabrakan untuk hash-table dari string yang memiliki panjang 5-10, dan cukup kecil untuk digunakan untuk perhitungan.
sumber
Hanya untuk memberikan sudut pandang alternatif ada situs ini:
http://www.codexon.com/posts/hash-functions-the-modulo-prime-myth
Yang berpendapat bahwa Anda harus menggunakan jumlah ember sebanyak mungkin sebagai lawan untuk membulatkan ke jumlah ember utama. Sepertinya kemungkinan yang masuk akal. Secara intuitif, saya tentu bisa melihat bagaimana jumlah ember yang lebih besar akan lebih baik, tetapi saya tidak dapat membuat argumen matematis tentang ini.
sumber
http://computinglife.wordpress.com/2008/11/20/why-do-hash-functions-use-prime-numbers/
sumber
Itu tergantung pada pilihan fungsi hash.
Banyak fungsi hash menggabungkan berbagai elemen dalam data dengan mengalikannya dengan beberapa faktor modulo kekuatan dua sesuai dengan ukuran kata mesin (bahwa modulus bebas dengan hanya membiarkan perhitungan melimpah).
Anda tidak ingin ada faktor umum antara pengganda untuk elemen data dan ukuran tabel hash, karena dengan demikian bisa terjadi bahwa memvariasikan elemen data tidak menyebar data ke seluruh tabel. Jika Anda memilih bilangan prima untuk ukuran tabel, faktor umum seperti itu sangat tidak mungkin.
Di sisi lain, faktor-faktor tersebut biasanya terdiri dari bilangan prima ganjil, jadi Anda juga harus aman menggunakan kekuatan dua untuk tabel hash Anda (misalnya Eclipse menggunakan 31 saat menghasilkan metode hashCode Java).
sumber
Misalkan ukuran meja Anda (atau angka untuk modulo) adalah T = (B * C). Sekarang jika hash untuk input Anda seperti (N * A * B) di mana N dapat berupa bilangan bulat, maka output Anda tidak akan didistribusikan dengan baik. Karena setiap kali n menjadi C, 2C, 3C dll, output Anda akan mulai berulang. yaitu output Anda akan didistribusikan hanya di posisi C. Perhatikan bahwa C di sini adalah (T / HCF (ukuran tabel, hash)).
Masalah ini dapat dihilangkan dengan membuat HCF 1. Angka prima sangat baik untuk itu.
Hal lain yang menarik adalah ketika T adalah 2 ^ N. Ini akan memberikan output yang persis sama dengan semua bit N input-hash yang lebih rendah. Karena setiap angka dapat diwakili kekuatan 2, ketika kita akan mengambil modulo dari angka apa pun dengan T, kita akan mengurangi semua kekuatan 2 angka bentuk, yaitu> = N, maka selalu memberikan jumlah pola tertentu, tergantung pada input . Ini juga pilihan yang buruk.
Demikian pula, T sebagai 10 ^ N buruk juga karena alasan yang sama (pola dalam notasi desimal angka bukan biner).
Jadi, bilangan prima cenderung memberikan hasil yang terdistribusi lebih baik, karenanya merupakan pilihan yang baik untuk ukuran tabel.
sumber
Saya percaya bahwa itu hanya ada hubungannya dengan fakta bahwa komputer bekerja dengan basis 2. Coba pikirkan bagaimana hal yang sama bekerja untuk basis 10:
Tidak masalah berapa angkanya: selama berakhir dengan 8, modulo 10-nya akan menjadi 8.
Memilih nomor yang cukup besar, non-power-of-two akan memastikan fungsi hash benar-benar merupakan fungsi dari semua bit input, bukan bagian dari mereka.
sumber
Saya ingin menambahkan sesuatu untuk jawaban Steve Jessop (saya tidak bisa mengomentarinya karena saya tidak memiliki reputasi yang cukup). Tetapi saya menemukan beberapa bahan yang membantu. Jawabannya sangat membantu tetapi dia membuat kesalahan: ukuran ember seharusnya tidak menjadi kekuatan 2. Saya hanya akan mengutip dari buku "Pengantar Algoritma" oleh Thomas Cormen, Charles Leisersen, dkk di halaman 263:
Semoga ini bisa membantu.
sumber
Untuk fungsi hash itu tidak hanya penting untuk meminimalkan tumbukan secara umum tetapi untuk membuatnya tidak mungkin untuk tetap dengan hash yang sama sambil mengumpulkan beberapa byte.
Katakanlah Anda memiliki persamaan:
(x + y*z) % key = x
dengan0<x<key
dan0<z<key
. Jika kuncinya adalah angka prima n * y = kunci benar untuk setiap n dalam N dan salah untuk setiap angka lainnya.Contoh di mana kunci bukan contoh utama: x = 1, z = 2 dan kunci = 8 Karena kunci / z = 4 masih merupakan bilangan alami, 4 menjadi solusi untuk persamaan kami dan dalam kasus ini (n / 2) * y = kunci benar untuk setiap n dalam N. Jumlah solusi untuk persamaan praktis dua kali lipat karena 8 bukan bilangan prima.
Jika penyerang kami sudah tahu bahwa 8 adalah solusi yang mungkin untuk persamaan tersebut, ia dapat mengubah file dari produksi 8 ke 4 dan masih mendapatkan hash yang sama.
sumber
Saya telah membaca situs web wordpress populer yang ditautkan dalam beberapa jawaban populer di atas di bagian atas. Dari apa yang saya mengerti, saya ingin berbagi pengamatan sederhana yang saya buat.
Anda dapat menemukan semua detail dalam artikel di sini , tetapi anggap hal berikut ini berlaku:
Implementasi hashmap umum ingin 2 hal menjadi unik.
Bagaimana cara mendapatkan indeks unik? Dengan membuat ukuran awal wadah internal menjadi prima juga. Jadi pada dasarnya, prime terlibat karena ia memiliki sifat unik untuk menghasilkan angka unik yang akhirnya kami gunakan untuk mengidentifikasi objek dan menemukan indeks di dalam wadah internal.
Contoh:
key = "key"
value = "value"
uniqueId = "k" * 31 ^ 2 + "e" * 31 ^ 1` + "y"
peta ke id unik
Sekarang kami ingin lokasi yang unik untuk nilai kami - kami juga
uniqueId % internalContainerSize == uniqueLocationForValue
, dengan asumsiinternalContainerSize
juga prima.Saya tahu ini disederhanakan, tetapi saya berharap untuk mendapatkan ide umum.
sumber
"Sifat dasar matematika" mengenai moduli daya utama adalah bahwa mereka adalah salah satu blok bangunan bidang terbatas . Dua blok bangunan lainnya adalah operasi penjumlahan dan perkalian. Sifat khusus moduli utama adalah bahwa mereka membentuk bidang terbatas dengan operasi penambahan dan perkalian "biasa", yang hanya dibawa ke modulus. Ini berarti setiap peta perkalian ke modul integer yang berbeda berbeda dengan prime, demikian pula setiap penambahan.
Moduli utama menguntungkan karena:
Namun mereka memiliki kelemahan besar, mereka membutuhkan divisi integer, yang membutuhkan banyak (~ 15-40) siklus, bahkan pada CPU modern. Dengan sekitar setengah perhitungan seseorang dapat memastikan hash tercampur dengan sangat baik. Dua operasi multiplikasi dan xorshift akan bercampur lebih baik daripada moudulus utama. Kemudian kita dapat menggunakan ukuran tabel hash apa saja dan pengurangan hash tercepat, memberikan 7 operasi total untuk kekuatan 2 ukuran tabel dan sekitar 9 operasi untuk ukuran sewenang-wenang.
Saya baru-baru ini melihat banyak implementasi tabel hash tercepat dan kebanyakan dari mereka tidak menggunakan moduli utama.
sumber
Pertanyaan ini digabung dengan pertanyaan yang lebih tepat, mengapa tabel hash harus menggunakan array berukuran prima, dan bukan kekuatan 2. Untuk fungsi hash itu sendiri ada banyak jawaban yang baik di sini, tetapi untuk pertanyaan terkait, mengapa beberapa tabel hash keamanan-kritis , seperti glibc, gunakan array berukuran prima, belum ada.
Secara umum kekuatan 2 tabel jauh lebih cepat. Ada yang mahal
h % n => h & bitmask
, di mana bitmask dapat dihitung melaluiclz
("hitung nol terkemuka") dari ukuran n. Fungsi modulo perlu melakukan pembagian integer yang sekitar 50x lebih lambat daripada yang logisand
. Ada beberapa trik untuk menghindari modulo, seperti menggunakan https://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction/ Lemire , tetapi umumnya tabel hash cepat menggunakan daya 2, dan tabel hash aman menggunakan bilangan prima.Kenapa begitu?
Keamanan dalam hal ini didefinisikan oleh serangan pada strategi resolusi tumbukan, yang dengan sebagian besar tabel hash hanya pencarian linear dalam daftar tumbukan terkait. Atau dengan tabel pengalamatan terbuka yang lebih cepat, cari secara langsung dalam tabel. Jadi dengan kekuatan 2 tabel dan beberapa pengetahuan internal tabel, misalnya ukuran atau urutan daftar kunci yang disediakan oleh beberapa antarmuka JSON, Anda mendapatkan jumlah bit yang tepat yang digunakan. Jumlah yang ada di bitmask. Ini biasanya lebih rendah dari 10 bit. Dan untuk 5-10 bit itu sepele untuk benturan paksa bahkan dengan fungsi hash yang paling kuat dan paling lambat. Anda tidak mendapatkan keamanan penuh dari fungsi hash 32bit atau 64 bit Anda lagi. Dan intinya adalah menggunakan fungsi hash kecil cepat, bukan monster seperti murmur atau bahkan siphash.
Jadi, jika Anda menyediakan antarmuka eksternal ke tabel hash Anda, seperti resolver DNS, bahasa pemrograman, ... Anda ingin peduli tentang penyalahgunaan orang-orang yang suka layanan seperti DOS. Biasanya lebih mudah bagi orang-orang seperti itu untuk mematikan layanan publik Anda dengan metode yang jauh lebih mudah, tetapi itu memang terjadi. Jadi orang peduli.
Jadi pilihan terbaik untuk mencegah dari serangan tabrakan tersebut adalah baik
1) untuk menggunakan tabel prima, karena itu
2) menggunakan langkah-langkah yang lebih baik terhadap serangan yang sebenarnya, bersama dengan kekuatan cepat 2 ukuran.
Ada mitos yang tersebar luas bahwa fungsi hash yang lebih aman membantu mencegah serangan seperti itu, yang salah seperti yang saya jelaskan. Tidak ada keamanan dengan bit rendah saja. Ini hanya akan bekerja dengan tabel berukuran prima, tetapi ini akan menggunakan kombinasi dari dua metode paling lambat, hash lambat ditambah modulo prime lambat.
Fungsi hash untuk tabel hash terutama harus kecil (tidak dapat dielakkan) dan cepat. Keamanan hanya dapat datang dari mencegah pencarian linear di tabrakan. Dan tidak menggunakan fungsi hash yang sepele, seperti yang tidak sensitif terhadap beberapa nilai (seperti \ 0 saat menggunakan perkalian).
Menggunakan benih acak juga merupakan pilihan yang baik, orang-orang mulai dengan yang pertama, tetapi dengan informasi yang cukup dari tabel bahkan benih acak tidak banyak membantu, dan bahasa dinamis biasanya membuatnya sepele untuk mendapatkan benih melalui metode lain, karena disimpan dalam lokasi memori yang dikenal.
sumber
sumber