xor
é uma função padrão perigosa a ser usada no hash. É melhor que and
e or
, mas isso não diz muito.
xor
é simétrico, então a ordem dos elementos é perdida. Então "bad"
, o hash combinará o mesmo que "dab"
.
xor
mapeia valores idênticos aos pares para zero e evite mapear valores "comuns" para zero:
Então, (a,a)
é mapeado para 0 e (b,b)
também para 0. Como esses pares são quase sempre mais comuns do que a aleatoriedade pode implicar, você acaba com muitas colisões em zero do que deveria.
Com esses dois problemas, xor
acaba sendo um combinador de hash que parece meio decente na superfície, mas não após uma inspeção mais aprofundada.
No hardware moderno, adicionar normalmente tão rápido quanto xor
(provavelmente usa mais energia para fazer isso, é certo). A tabela verdade de Adding é semelhante à xor
do bit em questão, mas também envia um bit para o próximo bit quando ambos os valores são 1. Isso significa que apaga menos informações.
Portanto, hash(a) + hash(b)
é melhor do hash(a) xor hash(b)
que se a==b
, o resultado for em hash(a)<<1
vez de 0.
Isso permanece simétrico; portanto, "bad"
e "dab"
obter o mesmo resultado continua sendo um problema. Podemos quebrar essa simetria por um custo modesto:
hash(a)<<1 + hash(a) + hash(b)
aka hash(a)*3 + hash(b)
. ( hash(a)
é recomendável calcular uma vez e armazenar se você usar a solução de turno). Qualquer constante ímpar, em vez de 3
, mapeará bijetivamente um k
número inteiro não assinado de "bits" para si próprio, pois o mapa em números inteiros não assinados é o módulo matemático 2^k
para alguns k
, e qualquer constante ímpar é relativamente primordial 2^k
.
Para uma versão ainda mais sofisticada, podemos examinar o boost::hash_combine
que é efetivamente:
size_t hash_combine( size_t lhs, size_t rhs ) {
lhs ^= rhs + 0x9e3779b9 + (lhs << 6) + (lhs >> 2);
return lhs;
}
aqui adicionamos algumas versões deslocadas de seed
com uma constante (que é basicamente aleatória se 0
es 1
- em particular, é o inverso da proporção áurea como uma fração de ponto fixo de 32 bits) com alguma adição e um xor. Isso quebra a simetria, e introduz alguns "ruído" se os valores hash de entrada são pobres (ou seja, imaginar cada hashes de componentes para 0 - as alças acima bem, gerando uma mancha de 1
e 0
. S após cada combinar meu ingênuo 3*hash(a)+hash(b)
simplesmente emite um 0
em Aquele caso).
(Para aqueles que não estão familiarizados com C / C ++, a size_t
é um valor inteiro não assinado que é grande o suficiente para descrever o tamanho de qualquer objeto na memória. Em um sistema de 64 bits, geralmente é um número inteiro não assinado de 64 bits. Em um sistema de 32 bits , um número inteiro não assinado de 32 bits.)