xor
jest niebezpieczną funkcją domyślną do użycia podczas mieszania. Jest lepszy niż and
i or
, ale to niewiele mówi.
xor
jest symetryczny, więc kolejność elementów zostaje utracona. Więc "bad"
hash będzie łączyć to samo co "dab"
.
xor
odwzorowuje parami identyczne wartości na zero i należy unikać mapowania „wspólnych” wartości na zero:
Jest więc (a,a)
mapowane na 0, a (b,b)
także na 0. Ponieważ takie pary są prawie zawsze bardziej powszechne, niż może to sugerować przypadkowość, kończy się z dużą liczbą kolizji na poziomie zerowym, niż powinieneś.
Z tymi dwoma problemami xor
kończy się sumatorem mieszania, który wygląda na w połowie przyzwoicie, ale nie po dalszej kontroli.
Na nowoczesnym sprzęcie, dodawanie zwykle tak szybko, jak xor
(trzeba przyznać, że prawdopodobnie zużywa więcej energii, aby to zrobić). Dodawanie tabeli prawdy jest podobne do tego xor
na danym bicie, ale wysyła również trochę do następnego bitu, gdy obie wartości są równe 1. Oznacza to, że usuwa mniej informacji.
Więc hash(a) + hash(b)
jest lepsze niż hash(a) xor hash(b)
w przypadku a==b
, gdy wynikiem jest hash(a)<<1
zamiast 0.
To pozostaje symetryczne; więc "bad"
i "dab"
otrzymuję ten sam rezultat pozostaje problemem. Możemy złamać tę symetrię niewielkim kosztem:
hash(a)<<1 + hash(a) + hash(b)
aka hash(a)*3 + hash(b)
. ( hash(a)
zaleca się jednorazowe obliczenie i przechowywanie, jeśli używasz rozwiązania zmianowego). Każda nieparzysta stała zamiast 3
będzie bijektywnie odwzorowywać " k
-bit" liczbę całkowitą bez znaku na siebie, ponieważ mapowanie na liczbach całkowitych bez znaku jest 2^k
dla niektórych matematyczne modulo k
, a każda nieparzysta stała jest względnie pierwsza 2^k
.
Aby uzyskać jeszcze bardziej wyszukaną wersję, możemy sprawdzić boost::hash_combine
, co jest efektywne:
size_t hash_combine( size_t lhs, size_t rhs ) {
lhs ^= rhs + 0x9e3779b9 + (lhs << 6) + (lhs >> 2);
return lhs;
}
tutaj dodajemy razem kilka przesuniętych wersji seed
ze stałą (która jest w zasadzie losowym 0
s i 1
s - w szczególności jest to odwrotność złotego podziału jako 32-bitowy ułamek z punktem stałym) z dodatkiem i xor. To łamie symetrię i wprowadza pewien „szum”, jeśli przychodzące wartości skrótu są słabe (tj. Wyobraź sobie, że każdy komponent hashuje do 0 - powyższe działa dobrze, generując rozmazanie 1
i 0
s po każdym połączeniu. Mój naiwny 3*hash(a)+hash(b)
po prostu wyświetla 0
in ta walizka).
(Dla tych, którzy nie znają języka C / C ++, a size_t
jest liczbą całkowitą bez znaku, która jest wystarczająco duża, aby opisać rozmiar dowolnego obiektu w pamięci. W systemie 64-bitowym jest to zwykle 64-bitowa liczba całkowita bez znaku. W systemie 32-bitowym , 32-bitowa liczba całkowita bez znaku).