HashCode'da neden asal bir sayı kullanmalıyım?


174

Sadece neden bir sınıfın hashCode()yönteminde bu primerlerin kullanıldığını merak ediyordum. Örneğin, benim hashCode()yöntem oluşturmak için Eclipse kullanırken her zaman 31kullanılan asal sayı vardır :

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

Referanslar:

İşte Hashcode üzerinde iyi bir astar ve bulduğum karma çalışmaların nasıl çalıştığına dair makale (C # ama kavramlar aktarılabilir): Eric Lippert'in GetHashCode () kuralları ve kuralları




1
Lütfen cevabımı stackoverflow.com/questions/1145217/… adresinden kontrol edin. Bu, bir alandaki polinomların (halka değil!) Özelliklerinden, dolayısıyla asal sayılardan kaynaklanmaktadır.
TT_

Yanıtlar:


104

Çünkü çarptığınız sayının ve eklediğiniz kova sayısının dikey asal çarpanlarına sahip olmasını istiyorsunuz.

Varmak için 8 kova olduğunu varsayalım. Eğer çarpmak için kullandığınız sayı 8'in katlarıysa, sokulan kova sadece en az önemli olan girişle (hiç çarpılmayan) belirlenir. Benzer girişler çarpışacaktır. Bir karma işlevi için iyi değil.

31, kova sayısının bölünmesi pek mümkün olmayacak kadar büyük bir başlangıçtır (ve aslında, modern java HashMap uygulamaları, kova sayısını 2 gücünde tutar).


9
Daha sonra 31 ile çarpılan bir karma işlevi optimal olmayan bir performans gösterir. Ancak, bir çarpan olarak ne kadar yaygın olduğu göz önüne alındığında, böyle bir karma tablo uygulamasını kötü tasarlanmış olarak değerlendiririm.
ILMTitan

11
Yani 31, hash tablosu uygulayıcılarının 31'in hash kodlarında yaygın olarak kullanıldığını bildikleri varsayımına dayanarak seçilir?
Steve Kuo

3
31, uygulamaların çoğunun nispeten küçük primer faktörleştirmeleri olduğu fikrine dayanarak seçilmiştir. Genellikle 2s, 3s ve 5s. 10'da başlayabilir ve çok dolduğunda 3 kat büyüyebilir. Boyut nadiren tamamen rastgele. Ve öyle olsa bile, 30/31 iyi senkronize edilmiş karma algoritmalara sahip olmak için kötü olasılıklar değildir. Diğerlerinin belirttiği gibi hesaplamak da kolay olabilir.
ILMTitan

8
Başka bir deyişle ... onları bu düzenlerden ayırmak için tasarlanmış bir işlev yazmak için girdi değerleri kümesi ve kümenin düzenliliği hakkında bir şeyler bilmemiz gerekir, böylece kümedeki değerler aynı şekilde çarpışmaz karma kovalar. Asal bir sayı ile çarpma / bölme / modüle etme, bunu etkiler, çünkü X öğeleri içeren bir LOOP'nuz varsa ve döngüde Y boşluklarını atlarsanız, X, Y faktörü oluncaya kadar asla aynı noktaya dönmezsiniz X genellikle 2'nin çift sayısı veya gücü olduğundan, Y'nin asal olması gerekir, bu nedenle X + X + X ... Y faktörü değildir, bu yüzden 31 yay! : /
Triynko

3
@FrankQ. Modüler aritmetiğin doğasıdır. (x*8 + y) % 8 = (x*8) % 8 + y % 8 = 0 + y % 8 = y % 8
ILMTitan

135

Asal sayılar, verileri karma kovalar arasında en iyi dağıtmak için seçilir. Girişlerin dağılımı rastgele ve eşit olarak yayılmışsa, karma kod / modül seçimi önemli değildir. Yalnızca girdiler için belirli bir model olduğunda bir etkisi vardır.

Bu genellikle bellek konumlarıyla uğraşırken geçerlidir. Örneğin, 32 bitlik tam sayıların tümü 4 ile bölünebilen adreslerle hizalanır. Birincil ve birincil olmayan modül kullanmanın etkilerini görselleştirmek için aşağıdaki tabloya göz atın:

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

Asal olmayan bir modül ile asal olmayan bir modül kullanırken neredeyse mükemmel dağılıma dikkat edin.

Bununla birlikte, yukarıdaki örnek büyük ölçüde kontrendiyse de, genel ilke, bir girdi örüntüsü ile uğraşırken , asal sayı modülü kullanmanın en iyi dağılımı vermesidir.


17
Hash kodunu oluşturmak için kullanılan çarpandan bahsetmiyoruz, bu karma kodlarını kovalara ayırmak için kullanılan modülodan değil mi?
ILMTitan

3
Aynı prensip. G / Ç açısından, karma, karma tablosunun modulo işlemine beslenir. Asıl mesele, eğer asal sayılarla çarparsanız, modülanın bile önemli olmadığı noktaya daha rastgele dağıtılmış girdiler elde edeceğinizdi. Karma işlevi, girdileri daha iyi dağıtma ve daha az düzenli hale getirme boşluğunu aldığından, bir kovaya yerleştirmek için kullanılan modulodan bağımsız olarak çarpışma olasılığı daha düşüktür.
Triynko

9
Bu tür bir cevap çok yararlıdır, çünkü birisine onlar için bir tane yakalamak yerine nasıl balık tutulacağını öğretmek gibidir. İnsanların karmalar için primler kullanmanın arkasındaki temel prensibi görmesine ve anlamasına yardımcı olur ...
Triynko

29

Etkili olduğu için, Etkili Java 2nd Edition matematik sorunu hakkında elinden feragat eder ve sadece 31'i seçmenin nedeninin şöyle olduğunu söyler:

  • Çünkü bu garip bir asal ve asalları kullanmak "geleneksel"
  • Ayrıca, bitsel optimizasyona izin veren iki güçten daha az

İşte Öğe 9'danhashCodeequals tam alıntı : Geçersiz kıldığınızda her zaman geçersiz kıl :

31 değeri seçildi, çünkü bu garip bir asal. Eğer eşitse ve çarpma taşmış olsaydı, 2 ile çarpma kaymaya eşdeğer olduğundan bilgi kaybolurdu. Asal kullanmanın avantajı daha az açıktır, ancak gelenekseldir.

31'in güzel bir özelliği, çarpmanın yerini bir kaydırma ( §15.19 ) ve daha iyi performans için çıkarma ile değiştirebilmesidir :

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

Modern VM'ler bu tür optimizasyonu otomatik olarak yapar.


Bu öğedeki tarif oldukça iyi hash fonksiyonları verirken, en son karma fonksiyonlar vermez veya Java platform kütüphaneleri 1.6 sürümünden itibaren hash fonksiyonları sağlamamaktadır. Bu tür karma fonksiyonları yazmak, en iyi matematikçiler ve teorik bilgisayar bilimcilerine bırakılan bir araştırma konusudur.

Belki de platformun daha sonraki bir sürümü, sınıfları için son teknoloji hash fonksiyonları ve ortalama programcıların bu hash fonksiyonlarını inşa etmelerine izin veren faydalı yöntemler sağlayacaktır. Bu arada, bu maddede açıklanan teknikler çoğu uygulama için yeterli olmalıdır.

Daha doğrusu, çok sayıda bölücüye sahip bir çarpan kullanmanın daha fazla karma çarpışmaya neden olacağı söylenebilir . Etkili karma için çarpışma sayısını en aza indirmek istediğimizden, daha az bölen içeren bir çarpan kullanmaya çalışıyoruz. Bir asal sayının tanımı gereği tam olarak iki farklı pozitif bölücü vardır.

İlgili sorular


4
Ha, ancak birçok uygun orada konum asal ya olan 2 ^ n + 1 (sözde Fermat asal ), örneğin, 3, 5, 17, 257, 65537ya da 2 ^ n - 1 ( Mersenne asal ) 3, 7, 31, 127, 8191, 131071, 524287, 2147483647. Ancak 31(ve diyelim ki 127) seçilmiştir.
Dmitry Bychenko

4
"çünkü bu garip bir başbakan" ... sadece bir tane bile başbakan var: P
Martin Schneider

"Etkili Java" içinde "daha az net, ama geleneksel" ifadelerini sevmiyorum. Eğer matematiksel ayrıntılara girmek istemiyorsa, onun yerine "benzer [matematiksel nedenleri var" gibi bir şey yazmalıdır.
Yazma şekli

5

Derleyicinin çarpmayı sola kaydırma 5 bite optimize edip değeri çıkarabilmesi için 31'in seçildiğini duydum.


derleyici bu şekilde nasıl optimizasyon yapabilir? x * 31 == x * 32-1 tüm x sonrası için geçerli değildir. Demek istediğiniz şey sola kaydırmaktı (eşittir 32 ile çarpın) ve sonra orijinal değeri (örneğimdeki x) çıkarın. Bu bir çarpma işleminden daha hızlı olsa da (bu arada modern işlemci işlemcileri için olasılık değildir), bir haschcode için bir çarpma seçerken dikkate alınması gereken daha önemli faktörler vardır (giriş değerlerinin kovalara eşit dağılımı akla gelir)
Grizzly

Biraz arama yapın, bu oldukça yaygın bir fikir.
Steve Kuo

4
Ortak görüş önemsizdir.
fraktor

1
@Grizzly, bu ise hızlı çarpma daha. IMul, herhangi bir modern CPU'da minimum 3 döngü gecikmesine sahiptir. (agner sis kılavuzlarına bakın) mov reg1, reg2-shl reg1,5-sub reg1,reg22 döngüde çalışabilir. (mov sadece bir yeniden adlandırmadır ve 0 döngü alır).
Johan

3

İşte kaynağa biraz daha yakın bir alıntı .

Aşağı kaynar:

  • 31 asaldır, bu da çarpışmaları azaltır
  • 31 iyi bir dağıtım üretir,
  • hızda makul bir değiş tokuş

3

İlk önce 2 ^ 32 karma değerini (^ boyutu int) hesaplarsınız , bu nedenle 2 ^ 32 için nispeten asal bir şey istersiniz (nispeten prime, ortak bölen olmadığı anlamına gelir). Bunun için herhangi bir tek sayı olurdu.

Sonra belirli bir hash tablosu için dizin genellikle hash tablosunun boyutundaki hash değeri modulo'sundan hesaplanır, bu nedenle hash tablosunun boyutuna nispeten asal olan bir şey istersiniz. Genellikle karma tabloların boyutları bu nedenle asal sayılar olarak seçilir. Java söz konusu olduğunda, Sun uygulaması, boyutun her zaman iki güç olduğundan emin olur, bu nedenle burada da tek bir sayı yeterlidir. Ayrıca, çarpışmaları daha da sınırlandırmak için hash anahtarlarına bazı ek masajlar da vardır.

Karma tablosu ve çarpanın ortak bir faktörü olsaydı kötü etki n, bazı durumlarda karma tablosuna yalnızca 1 / n girişlerinin kullanılması olabilir.


2

Asal sayıların kullanılmasının nedeni, veriler belirli kalıplar gösterdiğinde çarpışmaları en aza indirmektir.

İlk önce: Veriler rasgele ise, asal sayıya gerek yoktur, herhangi bir sayıya karşı mod işlemi yapabilirsiniz ve modülün her olası değeri için aynı sayıda çarpışmaya sahip olursunuz.

Ancak veriler rastgele olmadığında garip şeyler olur. Örneğin, her zaman 10'un katı olan sayısal verileri ele alalım.

Mod 4'ü kullanırsak şunları buluruz:

10 mod 4 = 2

20 mod 4 = 0

30 mod 4 = 2

40 mod 4 = 0

50 mod 4 = 2

Yani modülün 3 olası değerinden (0,1,2,3) sadece 0 ve 2'nin çarpışmaları olacaktır, bu kötüdür.

7 gibi asal bir sayı kullanırsak:

10 mod 7 = 3

20 mod 7 = 6

30 mod 7 = 2

40 mod 7 = 4

50 mod 7 = 1

vb

Ayrıca 5'in iyi bir seçim olmadığını, ancak 5'in asal nedeninin tüm anahtarlarımızın 5'in katları olması gerektiğini not ediyoruz. Bu, anahtarlarımızı bölmeyen bir asal sayı seçmemiz gerektiği, büyük bir asal sayı genellikle yeterlidir.

Bu nedenle, tekrarlı olmanın yanında, asal sayıların kullanılmasının nedeni, bir karma fonksiyonunun çarpışmalarının dağılımındaki tuşlardaki desenlerin etkisini nötralize etmektir.


1

31 ayrıca karma veri türü olarak int kullanan Java HashMap'e de özgüdür. Böylece maksimum kapasite 2 ^ 32'dir. Daha büyük Fermat veya Mersenne astarlarının kullanılmasının bir anlamı yoktur.


0

Genellikle, özellikle düşük entropi anahtarları için, karma kovalar arasında verilerinizin daha eşit bir şekilde dağıtılmasına yardımcı olur.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.