xor
es una función predeterminada peligrosa para usar cuando se usa hashing. Es mejor que and
y or
, pero eso no dice mucho.
xor
es simétrico, por lo que se pierde el orden de los elementos. Entonces "bad"
, el hash combinará lo mismo que "dab"
.
xor
asigna valores idénticos por pares a cero, y debe evitar asignar valores "comunes" a cero:
Por lo tanto, (a,a)
se asigna a 0, y (b,b)
también se asigna a 0. Como tales pares son casi siempre más comunes de lo que podría implicar la aleatoriedad, terminas con muchas colisiones en cero de lo que deberías.
Con estos dos problemas, xor
termina siendo un combinador de hash que parece medio decente en la superficie, pero no después de una inspección adicional.
En el hardware moderno, agregar generalmente casi tan rápido como xor
(probablemente use más potencia para lograr esto, es cierto). Agregar la tabla de verdad es similar al xor
bit en cuestión, pero también envía un bit al siguiente bit cuando ambos valores son 1. Esto significa que borra menos información.
Entonces hash(a) + hash(b)
es mejor que hash(a) xor hash(b)
en eso si a==b
, el resultado es en hash(a)<<1
lugar de 0.
Esto sigue siendo simétrico; por lo que el "bad"
y "dab"
conseguir el mismo resultado sigue siendo un problema. Podemos romper esta simetría por un costo modesto:
hash(a)<<1 + hash(a) + hash(b)
aka hash(a)*3 + hash(b)
. ( hash(a)
se recomienda calcular una vez y almacenar si usa la solución de turno). Cualquier constante impar en lugar de mapeará bijetivamente un entero sin signo 3
" k
-bit" consigo mismo, ya que el mapa en enteros sin signo es un módulo matemático 2^k
para algunos k
, y cualquier constante impar es relativamente primo 2^k
.
Para una versión aún más elegante, podemos examinar boost::hash_combine
, que es efectivamente:
size_t hash_combine( size_t lhs, size_t rhs ) {
lhs ^= rhs + 0x9e3779b9 + (lhs << 6) + (lhs >> 2);
return lhs;
}
aquí agregamos algunas versiones desplazadas de seed
con una constante (que es básicamente 0
s y 1
s al azar , en particular, es la inversa de la proporción áurea como una fracción de punto fijo de 32 bits) con alguna suma y un xor. Esto rompe la simetría, e introduce un poco de "ruido" si los valores hash entrantes son pobres (es decir, imaginar cada hashes componentes a 0 - las manijas por encima de ella, así, generar una mancha de 1
y 0
. S después de cada combinar mi ingenua 3*hash(a)+hash(b)
simplemente generan una 0
en Ese caso).
(Para aquellos que no están familiarizados con C / C ++, a size_t
es un valor entero sin signo que es lo suficientemente grande como para describir el tamaño de cualquier objeto en la memoria. En un sistema de 64 bits, generalmente es un entero sin signo de 64 bits. En un sistema de 32 bits , un entero sin signo de 32 bits).