Почему XOR-это способ объединения хэшей по умолчанию?


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

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

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

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

8 121

8 ответов:

предполагая равномерно случайные (1-битные) входы, распределение вероятности выхода и функции составляет 75%0 и 25% 1. И наоборот, или 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 наиболее подходит для цели, указанной в вашем вопросе?

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) один раз и хранение рекомендуется, если вы используете решение shift). Любая нечетная константа вместо 3 будет биективно отображать a 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 s и 1s -- в частности, это обратное золотому сечению как 32-битная фракция с фиксированной точкой) с некоторым добавлением и xor. Это нарушает симметрию, и вводит некоторый "шум", если входящие хэшированные значения плохи (т. е. Представьте, что каждый компонент хэшируется до 0 - выше хорошо обрабатывает его, создавая мазок 1 и 0s после каждого комбайна. Мой просто выводит 0).

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

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

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

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

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

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

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

сначала я хочу определить две простые функции, которые я буду использовать, чтобы объяснить это: 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, чтобы сделать их 1s. поэтому каждый нулевой бит остается нулевым битом, и каждый бит имеет шанс стать нулевым битом (и, следовательно, меньшим значением).

С: C = A OR B

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

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

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

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

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

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

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

исходный код для различных версий hashCode() in java.утиль.Массивы является отличным справочником для твердых, общего использования алгоритмов хэширования. Они легко понимаются и переводятся на другие языки программирования.

грубо говоря, большинство многопараметрической 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 Q & As для получения дополнительной информации о магии позади 31, и почему Java-код использует его так часто. Она несовершенна, но имеет очень хорошие характеристики.

покройте левые 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 ничего не выдает об этом входы.