Почему XOR является стандартным способом объединения хэшей? - PullRequest
131 голосов
/ 05 мая 2011

Скажем, у вас есть два хэша H(A) и H(B), и вы хотите объединить их. Я читал, что хороший способ объединить два хэша - это XOR их, например XOR( H(A), H(B) ).

Лучшее объяснение, которое я нашел, кратко затронуто в этих рекомендациях по хеш-функциям :

XOR с двумя числами с приблизительно случайным распределением приводит к другому числу, все еще с примерно случайным распределением *, но которое теперь зависит от этих двух значений.
...
* В каждом бите двух чисел, которые нужно объединить, выводится 0, если два бита равны, иначе - 1. Другими словами, в 50% комбинаций будет выводиться 1. Таким образом, если каждый из двух входных битов имеет примерно 50-50 шанс быть равным 0 или 1, то и выходной бит тоже будет.

Можете ли вы объяснить интуицию и / или математику, объясняющую, почему XOR должна быть операцией по умолчанию для объединения хеш-функций (а не OR или AND и т. Д.)?

Ответы [ 8 ]

147 голосов
/ 15 января 2015

xor - опасная функция по умолчанию, используемая при хешировании.Это лучше чем и и или или, но это не говорит о многом.

xor является симметричным, поэтому порядок элементов теряется.Таким образом, "bad" будет хешировать то же самое, что и "dab".

xor отображает идентичные значения в ноль, и вам следует избегать отображения "общих" значений в ноль:

Так что (a,a) получаетсопоставляется с 0, а (b,b) также сопоставляется с 0. Поскольку такие пары встречаются чаще, чем можно предположить по случайности, вы в конечном итоге столкнетесь с большим количеством столкновений в нуле, чем следует.

С этими двумя проблемами,xor в конечном итоге становится хеш-сумматором, который выглядит наполовину прилично на поверхности, но не после дальнейшего осмотра.

На современном оборудовании добавление обычно происходит примерно так же быстро, как и xor (вероятно, он использует больше энергии для этого, по общему признанию)).Таблица истинности добавления похожа на xor для рассматриваемого бита, но она также отправляет бит на следующий бит, когда оба значения равны 1. Это стирает меньше информации.

Так что hash(a) + hash(b) лучше, еслиa==b, результат вместо hash(a)<<1 вместо 0.

Это остается симметричным.Мы можем нарушить эту симметрию за скромные затраты:

hash(a)<<1 + hash(a) + hash(b)

или hash(a)*3 + hash(b).(вычисление hash(a) один раз и сохранение рекомендуется, если вы используете сменное решение).Любая нечетная константа вместо 3 будет биективно отображать size_t (или 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 с и 1 с - в частности, это инверсия золотого отношения как 32-битной дроби с фиксированной запятой) с некоторым добавлением и xor.Это нарушает симметрию и вносит некоторый «шум», если входящие значения хэширования являются плохими (т.е. представьте, что каждый компонент хеширует до 0 - вышеизложенный обрабатывает это хорошо, генерируя мазок 1 и 0 с после каждого объединения.Моя просто выводит 0).

Для тех, кто не знаком с C / C ++, size_t - это целочисленное значение без знака, которое достаточно велико, чтобы описать размер любого объекта в памяти.В 64-разрядной системе обычно это 64-разрядное целое число без знака.В 32-разрядной системе 32-разрядное целое число без знака.

109 голосов
/ 05 мая 2011

При условии равномерно случайных (1-битных) входов, распределение вероятности выхода функции AND составляет 75% 0 и 25% 1.И наоборот, OR равно 25% 0 и 75% 1.

Функция XOR равна 50% 0 и 50% 1, поэтому она подходит для объединения равномерных распределений вероятности.

Это можно увидеть, написав таблицы истинности:

 a | b | a AND b
---+---+--------
 0 | 0 |    0
 0 | 1 |    0
 1 | 0 |    0
 1 | 1 |    1

 a | b | a OR b
---+---+--------
 0 | 0 |    0
 0 | 1 |    1
 1 | 0 |    1
 1 | 1 |    1

 a | b | a XOR b
---+---+--------
 0 | 0 |    0
 0 | 1 |    1
 1 | 0 |    1
 1 | 1 |    0

Упражнение: Сколько логических функций двух 1-битных входов a и b имеют такое равномерное распределение выходов?Почему XOR наиболее подходит для цели, указанной в вашем вопросе?

29 голосов
/ 25 апреля 2013

Несмотря на удобные свойства смешивания битов, XOR является , а не хорошим способом объединения хэшей благодаря своей коммутативности. Подумайте, что произойдет, если вы сохранили перестановки {1, 2,…, 10} в хэш-таблице из 10 кортежей.

Гораздо лучший выбор - m * H(A) + H(B), где m - большое нечетное число.

Кредит: вышеупомянутый комбинатор был подсказкой от Боба Дженкинса.

16 голосов
/ 19 августа 2011

Xor может быть способом по умолчанию для объединения хэшей, но ответ Грега Хьюгилла также показывает, почему у него есть свои подводные камни: xor двух идентичных значений хэша равен нулю.В реальной жизни идентичные хэши встречаются чаще, чем можно было ожидать.Затем вы можете обнаружить, что в этих (не очень редких) угловых случаях результирующие комбинированные хэши всегда одинаковы (ноль).Хеш-коллизии будут намного, гораздо чаще, чем вы ожидаете.

В надуманном примере вы можете комбинировать хешированные пароли пользователей с разных веб-сайтов, которыми вы управляете.К сожалению, большое количество пользователей повторно использует свои пароли, и удивительная доля получаемых хэшей равна нулю!

8 голосов
/ 21 мая 2011

Я хочу кое-что указать другим людям, которые находят эту страницу.И и ИЛИ ограничивают вывод, как BlueRaja - Дэнни Пфлугхо пытается указать, но может быть лучше определен:

Сначала я хочу определить две простые функции, которые я буду использовать для объяснения этого: Min () и Max ().

Min (A, B) вернет меньшее значение между A и B, например: Min (1, 5) вернет 1.

Max (A, B)вернет значение, большее между A и B, например: Max (1, 5) возвращает 5.

Если вам дано: C = A AND B

Тогда вы можете найти, что C <= Min(A, B) Мы знаем это, потому что вы ничего не можете И с 0 битами A или B сделать их 1 с.Таким образом, каждый нулевой бит остается нулевым, и каждый бит имеет шанс стать нулевым (и, следовательно, меньшим значением).

С: C = A OR B

Верно обратное:C >= Max(A, B) При этом мы видим следствие функции AND.Любой бит, который уже равен единице, не может быть преобразован в ноль, поэтому он остается равным единице, но каждый нулевой бит имеет шанс стать единицей и, следовательно, большим числом.

Это означает, что состояниена вход накладывает ограничения на выход.Если вы И что-нибудь с 90, вы знаете, что выходной сигнал будет равен или меньше 90, независимо от того, что является другим значением.

Для XOR нет подразумеваемых ограничений, основанных на входах.Есть особые случаи, когда вы можете обнаружить, что если вы XOR байта с 255, то вы получите обратный, но любой возможный байт может быть выведен из этого.Каждый бит может изменить состояние в зависимости от того же бита в другом операнде.

2 голосов
/ 05 мая 2011

Если вы XOR случайный вход с предвзятым входом, выход будет случайным.То же самое не верно для AND или OR.Пример:

00101001 XOR 00000000 = 00101001
00101001 AND 00000000 = 00000000
00101001 OR  11111111 = 11111111

Как упоминает @Greg Hewgill, даже если оба входа являются случайными, использование AND или OR приведет к смещенному выводу.

Причина, по которой мы используем XOR над чем-то более сложным, заключается в том, что ну, в этом нет необходимости: XOR работает отлично, и это невероятно глупо-быстро.

1 голос
/ 23 мая 2017

Закройте 2 левых столбца и попытайтесь определить, какие входы используют только для вывода.

 a | b | a AND b
---+---+--------
 0 | 0 |    0
 0 | 1 |    0
 1 | 0 |    0
 1 | 1 |    1

Когда вы увидели 1-битный, вы должны были решить, что оба входа были 1.

Теперь сделайте то же самое для XOR

 a | b | a XOR b
---+---+--------
 0 | 0 |    0
 0 | 1 |    1
 1 | 0 |    1
 1 | 1 |    0

XOR ничего не выдает об этом.

0 голосов
/ 12 мая 2015

Исходный код для различных версий hashCode() в java.util.Arrays является отличным справочником для надежных алгоритмов хеширования общего назначения.Они легко понимаются и переводятся на другие языки программирования.

Грубо говоря, большинство реализаций hashCode() с несколькими атрибутами следуют этому шаблону:

public static int hashCode(Object a[]) {
    if (a == null)
        return 0;

    int result = 1;

    for (Object element : a)
        result = 31 * result + (element == null ? 0 : element.hashCode());

    return result;
}

Вы можете искать другие вопросы и ответы StackOverflow для получения дополнительной информации.о магии, стоящей за 31, и о том, почему код Java использует ее так часто.Он несовершенен, но имеет очень хорошие общие эксплуатационные характеристики.

...