xor
è una pericolosa funzione predefinita da utilizzare durante l'hash. È meglio di and
e or
, ma questo non dice molto.
xor
è simmetrico, quindi l'ordine degli elementi viene perso. Così"bad"
hash combina lo stesso di "dab"
.
xor
associa valori identici a coppie a zero e dovresti evitare di mappare valori "comuni" a zero:
Così (a,a)
viene mappato su 0 e(b,b)
anche mappato su 0. Poiché tali coppie sono quasi sempre più comuni di quanto la casualità possa implicare, si finisce con molte collisioni a zero di quanto si dovrebbe.
Con questi due problemi, xor
finisce per essere un combinatore di hash che sembra mezzo decente in superficie, ma non dopo un'ulteriore ispezione.
Su hardware moderno, l'aggiunta di solito è più veloce di xor
(probabilmente utilizza più potenza per farlo, lo ammetto). L'aggiunta della tabella di verità è simile axor
quella del bit in questione, ma invia anche un bit al bit successivo quando entrambi i valori sono 1. Ciò significa che cancella meno informazioni.
Quindi hash(a) + hash(b)
è meglio che hash(a) xor hash(b)
in questo se a==b
, il risultato èhash(a)<<1
invece di 0.
Questo rimane simmetrico; quindi lo "bad"
e "dab"
ottenere lo stesso risultato rimane un problema. Possiamo rompere questa simmetria per un costo modesto:
hash(a)<<1 + hash(a) + hash(b)
aka hash(a)*3 + hash(b)
. ( hash(a)
si consiglia di calcolare una volta e memorizzare se si utilizza la soluzione a turni). Qualsiasi costante dispari invece di 3
mappare biiettivamente un k
intero senza segno " -bit" su se stesso, poiché la mappa su numeri interi senza segno è un modulo di matematica 2^k
per alcuni k
, e qualsiasi costante dispari è relativamente primaria 2^k
.
Per una versione ancora più elaborata, possiamo esaminare boost::hash_combine
, che è effettivamente:
size_t hash_combine( size_t lhs, size_t rhs ) {
lhs ^= rhs + 0x9e3779b9 + (lhs << 6) + (lhs >> 2);
return lhs;
}
qui sommiamo alcune versioni spostate di seed
con una costante (che è sostanzialmente casuale 0
s e 1
s - in particolare è l'inverso del rapporto aureo come una frazione di punto fisso a 32 bit) con qualche aggiunta e uno xor. Ciò interrompe la simmetria e introduce un po 'di "rumore" se i valori di hash in entrata sono scarsi (cioè immagina che ogni componente abbia l'hash su 0 - quanto sopra lo gestisce bene, generando una sbavatura di 1
e 0
s dopo ogni combinazione. La mia ingenua 3*hash(a)+hash(b)
semplicemente genera un 0
in questo caso).
(Per coloro che non hanno familiarità con C / C ++, a size_t
è un valore intero senza segno che è abbastanza grande da descrivere la dimensione di qualsiasi oggetto in memoria. Su un sistema a 64 bit, di solito è un numero intero senza segno a 64 bit. Su un sistema a 32 bit , un numero intero senza segno a 32 bit.)