Digamos que você tenha dois hashes H(A)
e H(B)
deseje combiná-los. Eu li que uma boa maneira de combinar dois hashes é para XOR
eles, por exemplo XOR( H(A), H(B) )
.
A melhor explicação que encontrei foi abordada brevemente aqui nestas diretrizes de função de hash :
XORing dois números com distribuição aproximadamente aleatória resulta em outro número ainda com distribuição aproximadamente aleatória *, mas que agora depende dos dois valores.
...
* Em cada bit dos dois números a serem combinados, um 0 é emitido se os dois bits forem iguais, senão um 1. Em outras palavras, em 50% das combinações, um 1 será emitido. Portanto, se cada um dos dois bits de entrada tiver aproximadamente 50-50 chances de serem 0 ou 1, o mesmo ocorrerá com o bit de saída.
Você pode explicar a intuição e / ou a matemática por trás do motivo pelo qual o XOR deve ser a operação padrão para combinar funções de hash (em vez de OR ou AND etc.)?
cryptography
bit-manipulation
hash
probability
xor
Nate Murray
fonte
fonte
Respostas:
Supondo entradas uniformemente aleatórias (1 bit), a distribuição de probabilidade de saída da função AND é de 75%
0
e 25%1
. Por outro lado, o OR é de 25%0
e 75%1
.A função XOR é de 50%
0
e 50%1
, portanto, é boa para combinar distribuições de probabilidade uniformes.Isso pode ser visto escrevendo tabelas de verdade:
Exercício: Como muitas funções lógicas de duas entradas de 1 bit
a
eb
tem essa distribuição de saída uniforme? Por que o XOR é o mais adequado para o objetivo indicado em sua pergunta?fonte
(0, a & b, a > b, a, a < b, b, a % b, a | b, !a & !b, a == b, !b, a >= b, !a, a <= b, !a | !b, 1)
, as seguintes têm distribuições de 50% a 50% de 0s e 1s, assumindo que aeb têm distribuições de 50% a 50% de 0s e 1s:a, b, !a, !b, a % b, a == b
ou seja, o oposto de XOR (EQUIV) poderia ter sido usado como bem ...a, b, !a, !b
tenha a mesma distribuição que suas respectivas entradas, você perde a entropia da outra entrada. Ou seja, o XOR é mais adequado para o propósito de combinar hashes, porque queremos capturar entropia de a e b.(a,a)
e(b,b)
ambos produzem zero, o que em muitos casos (na maioria dos casos) aumenta muito a probabilidade de colisões em estruturas de dados baseadas em hash.xor
é uma função padrão perigosa a ser usada no hash. É melhor queand
eor
, 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 dohash(a) xor hash(b)
que sea==b
, o resultado for emhash(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: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 de3
, mapeará bijetivamente umk
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ático2^k
para algunsk
, e qualquer constante ímpar é relativamente primordial2^k
.Para uma versão ainda mais sofisticada, podemos examinar o
boost::hash_combine
que é efetivamente:aqui adicionamos algumas versões deslocadas de
seed
com uma constante (que é basicamente aleatória se0
es1
- 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 de1
e0
. S após cada combinar meu ingênuo3*hash(a)+hash(b)
simplesmente emite um0
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.)fonte
0x9e3779b9
.Apesar de suas práticas propriedades de mistura de bits, o XOR não é uma boa maneira de combinar hashes devido à sua comutatividade. Considere o que aconteceria se você armazenasse as permutações de {1, 2,…, 10} em uma tabela de 10 tuplas.
Uma escolha muito melhor é
m * H(A) + H(B)
, onde m é um grande número ímpar.Crédito: O combinador acima foi uma dica de Bob Jenkins.
fonte
long
e depois mover a parte superior de volta com a parte inferior.m = 3
é realmente uma boa escolha e muito rápido em muitos sistemas. Observe que, para qualquer númerom
inteiro ímpar, a multiplicação é modulo2^32
ou2^64
e, portanto, é invertível para que você não perca nenhum bit.Xor pode ser a maneira "padrão" de combinar hashes, mas a resposta de Greg Hewgill também mostra por que tem suas armadilhas: O xor de dois valores de hash idênticos é zero. Na vida real, existem hashes idênticos e são mais comuns do que se poderia esperar. Você pode descobrir que, nesses casos de canto (não tão pouco frequentes), os hashes combinados resultantes são sempre os mesmos (zero). As colisões de hash seriam muito, muito mais frequentes do que o esperado.
Em um exemplo artificial, você pode combinar senhas com hash de usuários de diferentes sites que você gerencia. Infelizmente, um grande número de usuários reutiliza suas senhas, e uma proporção surpreendente dos hashes resultantes é zero!
fonte
Há algo que quero destacar explicitamente para outras pessoas que encontram esta página. AND e OR restringem a saída como BlueRaja - Danny Pflughoe está tentando apontar, mas pode ser melhor definido:
Primeiro, quero definir duas funções simples que vou usar para explicar isso: Min () e Max ().
Min (A, B) retornará o valor menor entre A e B, por exemplo: Min (1, 5) retorna 1.
Max (A, B) retornará o valor que é maior entre A e B, por exemplo: Max (1, 5) retorna 5.
Se você receber:
C = A AND B
Então você pode achar que
C <= Min(A, B)
sabemos disso porque não há nada que você possa AND com os 0 bits de A ou B para torná-los 1s. Portanto, todo bit zero permanece um bit zero e cada bit tem a chance de se tornar um bit zero (e, portanto, um valor menor).Com:
C = A OR B
O oposto é verdadeiro:
C >= Max(A, B)
com isso, vemos o corolário da função AND. Qualquer bit que já seja um não pode ser transformado em zero, portanto permanece um, mas cada bit zero tem a chance de se tornar um e, portanto, um número maior.Isso implica que o estado da entrada aplique restrições à saída. Se você AND qualquer coisa com 90, você sabe que a saída será igual ou menor que 90, independentemente do outro valor.
Para o XOR, não há restrição implícita com base nas entradas. Há casos especiais em que você pode descobrir que, se você XOR um byte com 255, obtém o inverso, mas qualquer byte possível pode ser gerado a partir dele. Cada bit tem a chance de mudar de estado, dependendo do mesmo bit no outro operando.
fonte
OR
é no máximo bit a bit , eAND
é bit a bit min .Se você for
XOR
uma entrada aleatória com uma entrada tendenciosa, a saída será aleatória. O mesmo não é verdadeiro paraAND
ouOR
. Exemplo:Como o @Greg Hewgill menciona, mesmo se as duas entradas forem aleatórias, usando
AND
ouOR
resultarão em resultados tendenciosos.A razão pela qual usamos
XOR
algo mais complexo é que, bem, não há necessidade:XOR
funciona perfeitamente e é incrivelmente rápido e estúpido.fonte
Cubra as 2 colunas da esquerda e tente descobrir o que as entradas estão usando apenas a saída.
Quando você viu um bit, deveria ter percebido que as duas entradas eram 1.
Agora faça o mesmo para o XOR
O XOR não fornece nada sobre isso.
fonte
O código-fonte para várias versões do
hashCode()
in java.util.Arrays é uma excelente referência para algoritmos de hash sólidos e de uso geral. Eles são facilmente entendidos e traduzidos para outras linguagens de programação.Grosso modo, a maioria dos multi-atributo
hashCode()
implementações seguem este padrão:Você pode pesquisar outras Perguntas e Respostas sobre o StackOverflow para obter mais informações sobre a mágica por trás
31
e por que o código Java a usa com tanta frequência. É imperfeito, mas possui muito boas características de desempenho geral.fonte
string
com ostring + "AA"
IIRC) e há muito tempo eles desejavam não ter inserido esse algoritmo nas especificações. Dito isto, usar um número ímpar maior com mais bits definidos e adicionar mudanças ou rotações corrige esse problema. O 'mix' de MurmurHash3 faz isso.O XOR não ignora algumas das entradas às vezes como OR e AND .
Se você pegar AND (X, Y), por exemplo, e alimentar a entrada X com false, então a entrada Y não importa ... e provavelmente você gostaria que a entrada importasse ao combinar hashes.
Se você usar XOR (X, Y) , ambas as entradas importam SEMPRE . Não haveria valor de X onde Y não importa. Se X ou Y forem alterados, a saída refletirá isso.
fonte