Yanıtlar:
Knuth'un çarpımsal yöntemi:
hash(i)=i*2654435761 mod 2^32
Genel olarak, karma boyutunuza göre ( 2^32
örnekte) ve onunla ortak faktörleri olmayan bir çarpan seçmelisiniz . Bu şekilde, karma işlevi tüm karma alanınızı eşit şekilde kaplar.
Düzenleme: Bu hash fonksiyonunun en büyük dezavantajı, bölünebilirliği korumasıdır, bu nedenle tam sayılarınızın tümü 2'ye veya 4'e bölünebiliyorsa (bu nadir değildir), hash'leri de olacaktır. Bu, hash tablolarında bir sorundur - kullanılan kovaların yalnızca 1 / 4'ünü elde edebilirsiniz.
Aşağıdaki algoritmanın çok iyi bir istatistiksel dağılım sağladığını buldum. Her giriş biti, her çıkış bitini yaklaşık% 50 olasılıkla etkiler. Çarpışma yoktur (her girdi farklı bir çıktıyla sonuçlanır). Algoritma, CPU'nun yerleşik bir tam sayı çarpma birimine sahip olmaması dışında hızlıdır. C kodu, varsayılarak int
32 bit (Java, değiştirin >>
ile >>>
ve kaldırma unsigned
):
unsigned int hash(unsigned int x) {
x = ((x >> 16) ^ x) * 0x45d9f3b;
x = ((x >> 16) ^ x) * 0x45d9f3b;
x = (x >> 16) ^ x;
return x;
}
Sihirli sayı, saatlerce çalışan ve çığ etkisini (tek bir giriş biti değiştirildiğinde değişen çıktı bitlerinin sayısı; ortalama olarak yaklaşık 16 olmalıdır) hesaplayan özel bir çok iş parçacıklı test programı kullanılarak hesaplandı. çıkış biti değişiklikleri (çıkış bitleri birbirine bağlı olmamalıdır) ve herhangi bir giriş biti değiştirilirse her çıkış bitindeki bir değişiklik olasılığı. Hesaplanan değerler, MurmurHash tarafından kullanılan 32-bit sonlandırıcıdan daha iyidir ve neredeyse AES kullanılırkenki kadar iyidir (tam olarak değil) . Küçük bir avantaj, aynı sabitin iki kez kullanılmasıdır (son test ettiğimde biraz daha hızlı hale getirdi, hala böyle olup olmadığından emin değilim).
Şunu ( çarpımsal tersi ) 0x45d9f3b
ile değiştirirseniz, işlemi tersine çevirebilirsiniz (hash'den girdi değerini elde edebilirsiniz ):0x119de1f3
unsigned int unhash(unsigned int x) {
x = ((x >> 16) ^ x) * 0x119de1f3;
x = ((x >> 16) ^ x) * 0x119de1f3;
x = (x >> 16) ^ x;
return x;
}
64 bitlik sayılar için, en hızlı olmayabileceğini düşünseniz bile aşağıdakileri kullanmanızı öneririm. Bu , Better Bit Mixing (mix 13) adlı blog makalesine dayanıyor gibi görünen splitmix64'e dayanıyor .
uint64_t hash(uint64_t x) {
x = (x ^ (x >> 30)) * UINT64_C(0xbf58476d1ce4e5b9);
x = (x ^ (x >> 27)) * UINT64_C(0x94d049bb133111eb);
x = x ^ (x >> 31);
return x;
}
Java, kullanım için long
, eklemek L
yerine, sabite >>
ile >>>
ve çıkarın unsigned
. Bu durumda, ters çevirmek daha karmaşıktır:
uint64_t unhash(uint64_t x) {
x = (x ^ (x >> 31) ^ (x >> 62)) * UINT64_C(0x319642b2d24d8ec3);
x = (x ^ (x >> 27) ^ (x >> 54)) * UINT64_C(0x96de1b173f119089);
x = x ^ (x >> 30) ^ (x >> 60);
return x;
}
Güncelleme: Diğer (muhtemelen daha iyi) sabitlerin listelendiği Hash Function Prospector projesine de bakmak isteyebilirsiniz .
x = ((x >> 32) ^ x)
ve ardından yukarıdaki 32 bit çarpımlarını kullanın. Hangisinin daha iyi olduğundan emin değilim. Ayrıca Murmur3
Verilerinizin nasıl dağıtıldığına bağlıdır. Basit bir sayaç için en basit işlev
f(i) = i
iyi olacak (optimal olduğundan şüpheleniyorum ama bunu kanıtlayamıyorum).
.hashCode()
, buraya bakın .
Hızlı ve iyi hash fonksiyonları, daha az kaliteye sahip hızlı permütasyonlardan oluşturulabilir.
Şununla gösterildiği gibi, üstün niteliklere sahip bir hash işlevi sağlamak için Rastgele sayı üretimi için PCG .
Aslında bu aynı zamanda rrxmrrxmsx_0 ve üfürüm hash'in bilerek veya bilmeyerek kullandığı tariftir.
Şahsen buldum
uint64_t xorshift(const uint64_t& n,int i){
return n^(n>>i);
}
uint64_t hash(const uint64_t& n){
uint64_t p = 0x5555555555555555ull; // pattern of alternating 0 and 1
uint64_t c = 17316035218449499591ull;// random uneven integer constant;
return c*xorshift(p*xorshift(n,32),32);
}
yeterince iyi olmak için.
İyi bir hash işlevi,
Önce kimlik işlevine bakalım. 1. tatmin eder ama 2. değildir:
Giriş biti n,% 100 (kırmızı) bir korelasyon ile çıkış biti n'yi belirler ve diğerleri yoktur, bu nedenle bunlar mavidir ve boyunca mükemmel bir kırmızı çizgi verir.
Bir xorshift (n, 32) çok daha iyi değildir, bir buçuk çizgi verir. Yine de tatmin edici 1., çünkü ikinci bir uygulamayla ters çevrilebilir.
İşaretsiz bir tamsayı ile çarpma çok daha iyidir, daha güçlü bir şekilde basamaklanır ve 0,5 olasılıkla daha fazla çıktı bitini çevirir, yeşil renkte istediğiniz şey budur. Her bir eşit olmayan tam sayı için çarpımsal bir tersi olduğu için 1'i karşılar.
İkisini birleştirmek, aşağıdaki çıktıyı verir, ancak iki önyargılı işlevin bileşimi başka bir önyargılı işlev verir.
İkinci bir çarpma ve xorshift uygulaması aşağıdakileri verecektir:
Veya GHash gibi Galois alan çarpımlarını kullanabilirsiniz , bunlar modern CPU'larda oldukça hızlı hale gelirler ve tek adımda üstün niteliklere sahiptirler.
uint64_t const inline gfmul(const uint64_t& i,const uint64_t& j){
__m128i I{};I[0]^=i;
__m128i J{};J[0]^=j;
__m128i M{};M[0]^=0xb000000000000000ull;
__m128i X = _mm_clmulepi64_si128(I,J,0);
__m128i A = _mm_clmulepi64_si128(X,M,0);
__m128i B = _mm_clmulepi64_si128(A,M,0);
return A[0]^A[1]^B[1]^X[0]^X[1];
}
__m128i I = i; //set the lower 64 bits
, ama yapamıyorum, bu yüzden kullanıyorum ^=
. 0^1 = 1
bu nedenle dahil değildir. {}
Derleyicimle başlatmaya gelince asla şikayet etmedi, bu en iyi çözüm olmayabilir, ancak bununla istediğim şey hepsini 0'a başlatmak, böylece yapabilirim ^=
veya |=
. Sanırım bu kodu, aynı zamanda ters çevirme sağlayan bu blog gönderisine dayandırdım , çok faydalı: D
Bu sayfa , genel olarak düzgün bir şekilde olma eğiliminde olan bazı basit hash fonksiyonlarını listeler, ancak herhangi bir basit hash'in iyi çalışmadığı patolojik durumlar vardır.
32 bit çarpma yöntemi (çok hızlı) bkz. @Rafal
#define hash32(x) ((x)*2654435761)
#define H_BITS 24 // Hashtable size
#define H_SHIFT (32-H_BITS)
unsigned hashtab[1<<H_BITS]
....
unsigned slot = hash32(x) >> H_SHIFT
32 bit ve 64 bit (iyi dağıtım) şurada : MurmurHash
Eternally Confuzzled'da bazı karma algoritmalar hakkında güzel bir genel bakış var . Bob Jenkins'in hızla çığa ulaşan ve bu nedenle verimli hash tablosu araması için kullanılabilen bir seferde tek seferde hashini öneririm.
Cevap, aşağıdaki gibi birçok şeye bağlıdır:
SHA-1 gibi Merkle-Damgard hash fonksiyon ailesine bir göz atmanızı öneririm .
Verilerinizi önceden bilmeden bir hash fonksiyonunun "iyi" olduğunu söyleyebileceğimizi sanmıyorum! ve onunla ne yapacağını bilmeden.
Bilinmeyen veri boyutları için karma tablolardan daha iyi veri yapıları vardır (burada bir karma tablo için karma işlemi yaptığınızı varsayıyorum). Sınırlı miktarda bellekte depolanması gereken "sonlu" sayıda öğeye sahip olduğumu bildiğimde kişisel olarak bir karma tablo kullanırdım. Karma fonksiyonum hakkında düşünmeye başlamadan önce verilerim üzerinde hızlı bir istatistiksel analiz yapmayı dener ve yapar, nasıl dağıtıldığını görmek vb.
Rastgele hash değerleri için, bazı mühendisler altın oran asal sayısının (2654435761) kötü bir seçim olduğunu söyledi, test sonuçlarımla bunun doğru olmadığını anladım; bunun yerine, 2654435761 hash değerlerini oldukça iyi dağıtır.
#define MCR_HashTableSize 2^10
unsigned int
Hash_UInt_GRPrimeNumber(unsigned int key)
{
key = key*2654435761 & (MCR_HashTableSize - 1)
return key;
}
Karma tablo boyutu ikinin üssü olmalıdır.
Tamsayılar için birçok hash fonksiyonunu değerlendirmek için bir test programı yazdım, sonuçlar GRPrimeNumber'ın oldukça iyi bir seçim olduğunu gösteriyor.
Denedim:
Test sonuçlarımla, Altın Oran Asal Sayısının her zaman daha az boş kova veya sıfır boş kova ve en kısa çarpışma zinciri uzunluğuna sahip olduğunu buldum.
Tamsayılar için bazı hash işlevlerinin iyi olduğu iddia edilmektedir, ancak test sonuçları, total_data_entry / total_bucket_number = 3 olduğunda, en uzun zincir uzunluğunun 10'dan büyük olduğunu (maks. Çarpışma sayısı> 10) ve birçok kepçenin eşlenmediğini (boş kovalar ), sıfır boş kova ve Altın Oran Asal Sayı Karma ile en uzun zincir uzunluğu 3 sonucuyla karşılaştırıldığında çok kötü.
BTW, test sonuçlarımla, xor hash işlevlerini kaydırmanın bir versiyonunun oldukça iyi olduğunu buldum (mikera tarafından paylaşılıyor).
unsigned int Hash_UInt_M3(unsigned int key)
{
key ^= (key << 13);
key ^= (key >> 17);
key ^= (key << 5);
return key;
}
Bu konuyu bulduğumdan beri kullanıyorum splitmix64
(Thomas Mueller'in cevabına işaret ediyor ). Bununla birlikte, yakın zamanda Pelle Evensen'in orijinal MurmurHash3 sonlandırıcısından ve onun haleflerinden ( splitmix64
ve diğer karışımlardan) çok daha iyi istatistiksel dağılım sağlayan rrxmrrxmsx_0'e rastladım . İşte C'deki kod parçacığı:
#include <stdint.h>
static inline uint64_t ror64(uint64_t v, int r) {
return (v >> r) | (v << (64 - r));
}
uint64_t rrxmrrxmsx_0(uint64_t v) {
v ^= ror64(v, 25) ^ ror64(v, 50);
v *= 0xA24BAED4963EE407UL;
v ^= ror64(v, 24) ^ ror64(v, 49);
v *= 0x9FB21C651E98DF25UL;
return v ^ v >> 28;
}
Pelle ayrıca son adımda kullanılan 64 bitlik karıştırıcının ve daha yeni varyantların derinlemesine bir analizini sağlarMurmurHash3
.