xor
опасная функция по умолчанию для использования при хешировании Это лучше чем and
и or
, но это мало что говорит.
xor
симметричен, поэтому порядок элементов теряется. Так "bad"
что хеш объединит так же как "dab"
.
xor
отображает попарно одинаковые значения в ноль, и вам следует избегать отображения «общих» значений в ноль:
Таким образом, (a,a)
отображается на 0, а (b,b)
также отображается на 0. Так как такие пары почти всегда более распространены, чем может предполагать случайность, вы в конечном итоге столкнетесь с большим количеством столкновений в нуле, чем должны.
С этими двумя проблемами xor
получается хеш-сумматор, который выглядит наполовину прилично на поверхности, но не после дальнейшей проверки.
На современном оборудовании добавление обычно происходит примерно так же быстро xor
(вероятно, для этого требуется больше энергии). Таблица истинности добавления похожа наxor
на рассматриваемый бит, но она также отправляет бит на следующий бит, когда оба значения равны 1. Это означает, что она стирает меньше информации.
Так hash(a) + hash(b)
что лучше, чем hash(a) xor hash(b)
в том случае a==b
, если результатhash(a)<<1
вместо 0 результат.
Это остается симметричным; поэтому "bad"
и "dab"
получение того же результата остается проблемой. Мы можем нарушить эту симметрию за скромную цену:
hash(a)<<1 + hash(a) + hash(b)
ака hash(a)*3 + hash(b)
. (Расчет hash(a)
один раз и сохранение рекомендуется, если вы используете сменное решение). Любая нечетная константа вместо " 3
биективно" будет отображать " k
-битное" целое число без знака для себя, так как отображение на целые числа без знака является математическим по модулю2^k
для некоторых k
, и любая нечетная константа относительно проста 2^k
.
Для еще более изящной версии мы можем изучить boost::hash_combine
, что эффективно:
size_t hash_combine( size_t lhs, size_t rhs ) {
lhs ^= rhs + 0x9e3779b9 + (lhs << 6) + (lhs >> 2);
return lhs;
}
здесь мы складываем несколько сдвинутых версий seed
с константой (которая в основном случайная 0
s и 1
s - в частности, это обратное значение золотого сечения как 32-битной дроби с фиксированной запятой) с некоторым добавлением и xor. Это нарушает симметрию и вводит некоторый «шум», если входящие хэшированные значения плохие (т.е. представьте, что каждый компонент хеширует до 0 - вышеупомянутый обрабатывает это хорошо, генерируя мазок 1
и 0
s после каждого объединения. Мой наивный 3*hash(a)+hash(b)
просто выводит a0
in тот случай).
(Для тех, кто не знаком с C / C ++, a size_t
- это целое число без знака, которое достаточно велико, чтобы описать размер любого объекта в памяти. В 64-битной системе это обычно 64-битное целое число без знака. В 32-битной системе 32-разрядное целое число без знака.)