Risposte:
Il metodo moltiplicativo di Knuth:
hash(i)=i*2654435761 mod 2^32
In generale, dovresti scegliere un moltiplicatore che sia nell'ordine della tua dimensione hash ( 2^32
nell'esempio) e non abbia fattori comuni con esso. In questo modo la funzione hash copre tutto lo spazio hash in modo uniforme.
Modifica: il più grande svantaggio di questa funzione hash è che conserva la divisibilità, quindi se i tuoi numeri interi sono tutti divisibili per 2 o per 4 (il che non è raro), anche i loro hash lo saranno. Questo è un problema nelle tabelle hash: potresti ritrovarti con solo 1/2 o 1/4 dei bucket utilizzati.
Ho trovato che il seguente algoritmo fornisce un'ottima distribuzione statistica. Ogni bit di ingresso influenza ogni bit di uscita con circa il 50% di probabilità. Non ci sono collisioni (ogni input risulta in un output diverso). L'algoritmo è veloce tranne se la CPU non dispone di un'unità di moltiplicazione di numeri interi incorporata. Codice C, supponendo che int
sia a 32 bit (per Java, sostituire >>
con >>>
e rimuovere unsigned
):
unsigned int hash(unsigned int x) {
x = ((x >> 16) ^ x) * 0x45d9f3b;
x = ((x >> 16) ^ x) * 0x45d9f3b;
x = (x >> 16) ^ x;
return x;
}
Il numero magico è stato calcolato utilizzando uno speciale programma di test multi-thread che ha funzionato per molte ore, che calcola l'effetto valanga (il numero di bit di uscita che cambiano se viene cambiato un singolo bit di ingresso; dovrebbe essere in media quasi 16), indipendenza di i bit di uscita cambiano (i bit di uscita non dovrebbero dipendere l'uno dall'altro) e la probabilità di un cambiamento in ogni bit di uscita se viene modificato un bit di ingresso. I valori calcolati sono migliori del finalizzatore a 32 bit utilizzato da MurmurHash e quasi altrettanto buoni (non del tutto) come quando si utilizza AES . Un leggero vantaggio è che la stessa costante viene utilizzata due volte (l'ultima volta che l'ho testata l'ha resa leggermente più veloce, non sono sicuro che sia ancora così).
È possibile invertire il processo (ottenere il valore di input dall'hash) se si sostituisce 0x45d9f3b
con 0x119de1f3
(l' inverso moltiplicativo ):
unsigned int unhash(unsigned int x) {
x = ((x >> 16) ^ x) * 0x119de1f3;
x = ((x >> 16) ^ x) * 0x119de1f3;
x = (x >> 16) ^ x;
return x;
}
Per i numeri a 64 bit, suggerisco di utilizzare quanto segue, anche se potrebbe non essere il più veloce. Questo è basato su splitmix64 , che sembra essere basato sull'articolo del blog Better Bit Mixing (mix 13).
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;
}
Per Java, usa long
, aggiungi L
alla costante, sostituisci >>
con >>>
e rimuovi unsigned
. In questo caso, la retromarcia è più complicata:
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;
}
Aggiornamento: potresti anche voler esaminare il progetto Hash Function Prospector , dove sono elencate altre costanti (possibilmente migliori).
x = ((x >> 32) ^ x)
e poi usa le moltiplicazioni a 32 bit sopra. Non sono sicuro di cosa sia meglio. Potresti anche voler guardare il finalizzatore
Dipende da come vengono distribuiti i dati. Per un semplice contatore, la funzione più semplice
f(i) = i
sarà buono (sospetto ottimale, ma non posso provarlo).
Le funzioni hash veloci e buone possono essere composte da permutazioni veloci con qualità minori, come
Per produrre una funzione di hashing con qualità superiori, come dimostrato con PCG per la generazione di numeri casuali.
Questa è infatti anche la ricetta che rrxmrrxmsx_0 e il murmur hash stanno usando, consapevolmente o inconsapevolmente.
Ho trovato personalmente
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);
}
essere abbastanza bravo.
Una buona funzione hash dovrebbe
Diamo prima un'occhiata alla funzione di identità. Soddisfa 1. ma non 2.:
Il bit di ingresso n determina il bit di uscita n con una correlazione del 100% (rosso) e nessun altro, sono quindi blu, dando una linea rossa perfetta attraverso.
Uno xorshift (n, 32) non è molto meglio, producendo una linea e mezza. Ancora soddisfacente 1., perché invertibile con una seconda applicazione.
Una moltiplicazione con un intero senza segno è molto meglio, a cascata in modo più forte e capovolgendo più bit di output con una probabilità di 0,5, che è quello che vuoi, in verde. Soddisfa 1. poiché per ogni numero intero dispari c'è un inverso moltiplicativo.
La combinazione dei due dà il seguente output, ancora soddisfacente 1. poiché la composizione di due funzioni biiettive produce un'altra funzione biiettiva.
Una seconda applicazione di moltiplicazione e xorshift produrrà quanto segue:
Oppure puoi usare le moltiplicazioni di campo di Galois come GHash , sono diventate ragionevolmente veloci sulle moderne CPU e hanno qualità superiori in un unico passaggio.
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
, ma non posso, quindi sto usando ^=
. 0^1 = 1
quindi no non coinvolto. Per quanto riguarda l'inizializzazione con il {}
mio compilatore non mi sono mai lamentato, potrebbe non essere la soluzione migliore, ma quello che voglio è inizializzare tutto a 0 così posso fare ^=
o |=
. Penso di aver basato quel codice su questo post del blog che dà anche l'inversione, molto utile: D
Questa pagina elenca alcune semplici funzioni di hash che tendono ad essere decentemente in generale, ma qualsiasi hash semplice ha casi patologici in cui non funziona bene.
Metodo moltiplicativo a 32 bit (molto veloce) vedi @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 e 64 bit (buona distribuzione) a: MurmurHash
C'è una bella panoramica su alcuni algoritmi di hash su Eternally Confuzzled . Consiglierei l'hash one-at-a-time di Bob Jenkins che raggiunge rapidamente una valanga e quindi può essere utilizzato per un'efficiente ricerca nella tabella hash.
La risposta dipende da molte cose come:
Suggerisco di dare un'occhiata alla famiglia di funzioni hash Merkle-Damgard come SHA-1 ecc
Non credo si possa dire che una funzione hash sia "buona" senza conoscere i propri dati in anticipo! e senza sapere cosa farai con esso.
Esistono strutture di dati migliori delle tabelle hash per dimensioni di dati sconosciute (presumo che tu stia facendo l'hashing per una tabella hash qui). Personalmente userei una tabella hash quando so di avere un numero "finito" di elementi che devono essere memorizzati in una quantità limitata di memoria. Proverei a fare una rapida analisi statistica sui miei dati, vedere come sono distribuiti ecc. Prima di iniziare a pensare alla mia funzione hash.
Per i valori hash casuali, alcuni ingegneri hanno detto che il numero primo della sezione aurea (2654435761) è una cattiva scelta, con i risultati dei miei test, ho scoperto che non è vero; invece, 2654435761 distribuisce i valori hash abbastanza bene.
#define MCR_HashTableSize 2^10
unsigned int
Hash_UInt_GRPrimeNumber(unsigned int key)
{
key = key*2654435761 & (MCR_HashTableSize - 1)
return key;
}
La dimensione della tabella hash deve essere una potenza di due.
Ho scritto un programma di test per valutare molte funzioni hash per interi, i risultati mostrano che GRPrimeNumber è una buona scelta.
Ho provato:
Con i risultati dei miei test, ho scoperto che Golden Ratio Prime Number ha sempre meno secchi vuoti o zero secchi vuoti e la lunghezza della catena di collisione più corta.
Alcune funzioni hash per gli interi sono ritenute valide, ma i risultati dei test mostrano che quando total_data_entry / total_bucket_number = 3, la lunghezza della catena più lunga è maggiore di 10 (numero massimo di collisioni> 10) e molti bucket non sono mappati (bucket vuoti ), che è pessimo, rispetto al risultato di zero secchio vuoto e lunghezza della catena più lunga 3 di Golden Ratio Prime Number Hashing.
A proposito, con i risultati dei miei test, ho scoperto che una versione delle funzioni hash shifting-xor è abbastanza buona (è condivisa da mikera).
unsigned int Hash_UInt_M3(unsigned int key)
{
key ^= (key << 13);
key ^= (key >> 17);
key ^= (key << 5);
return key;
}
Ho usato splitmix64
(indicato nella risposta di Thomas Mueller ) da quando ho trovato questo thread. Tuttavia, di recente mi sono imbattuto in Pelle Evensen rrxmrrxmsx_0 di , che ha prodotto una distribuzione statistica incredibilmente migliore rispetto al finalizzatore MurmurHash3 originale e ai suoi successori ( splitmix64
e altri mix). Ecco lo snippet di codice in C:
#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 fornisce anche un'analisi approfondita del mixer a 64 bit utilizzato nella fase finale di MurmurHash3
e delle varianti più recenti.