Скользящий хеш

Материал из testwiki
Перейти к навигации Перейти к поиску

Скользящий хеш (Шаблон:Lang-en, также кольцевой хеш) — хеш-функция, обрабатывающая вход в рамках некоторого окна. Получение значения хеш-функции для сдвинутого окна в таких функциях является дешевой операцией. Для пересчета значения требуется знать лишь предыдущее значение хеша, значение входных данных, которые остались за пределами окна, и значение данных, которые попали в окно. Другими словами, если x=h(a1a2⋯an) представляет собой хеш последовательности a1a2⋯an, то хеш h(a2a3⋯anan+1) для «сдвинутой» последовательности a2a3⋯anan+1 может быть получен с помощью легко вычислимой функции f(x,a1,an+1).

Возможность быстрого «сдвига» хеша накладывает некоторые ограничения на теоретические гарантии. В частности, показаноШаблон:Sfn, что семейства кольцевых хешей не могут быть Шаблон:Iw; максимум — универсальными или Шаблон:Iw. Впрочем, для большинства приложений достаточно универсальности (даже приблизительной).

Кольцевой хеш применяется для поиска подстроки в алгоритме Рабина — Карпа, для вычисления хешей N-грамм в текстеШаблон:Sfn, а также в программе rsync для сравнения двоичных файлов (используется кольцевая версия adler-32).

Полиномиальный хеш

В алгоритме Рабина — Карпа часто используется простой полиномиальный кольцевой хеш, построенный на операциях умножения и сложенияШаблон:SfnШаблон:Sfn:

h(a1a2⋯an)=(a1xn−1+a2xn−2+a3xn−3+⋯+anx0)modq.

Чтобы избежать использования целочисленной арифметики произвольной точности, используется арифметика в кольце вычетов по модулю q, умещающемуся в одно машинное слово. Выбор констант x и q очень важен для получения качественного хеша. В исходном варианте хеша предполагалось, что q должно быть случайно выбранным простым числом, а x=2.Шаблон:Sfn Но ввиду того, что алгоритм выбора случайного простого числа не такой простой, предпочитают использовать вариант хеша, в котором q является фиксированным простым числом, а x выбирается случайно из диапазона {0,1,…,q−1}. Дитзфелбингер и др.Шаблон:Sfn показали, что такой вариант хеша имеет те же теоретические характеристики, что и исходный. В частности, вероятность совпадения значений хешей двух различных строк a1a2⋯an и b1b2⋯bn не превосходит 1/nc, если a1,…,an и b1,…,bn представляют собой целые числа из диапазона [0,q), q>nc+1 и x выбирается действительно случайно.

Удаление старых входных символов и добавление новых производится путём прибавления или вычитания первого или последнего члена формулы (по модулю q). Для удаления члена a1xn−1 хранят заранее посчитанное значение xn−1modq. Сдвиг окна производится путём домножения всего многочлена h(a1a2⋯an) на x либо делением на x (если q простое, то в кольце вычетов возможно вместо деления производить умножение на обратную величину). На практике удобнее всего полагать q=231−1 или q=261−1 для, соответственно, 32- и 64-битовых машинных слов (это так называемые простые числа Мерсенна). В таком случае операция взятия модуля может быть выполнена на многих компьютерах с помощью быстрых операций побитового сдвига и сложения[1]. Другой возможный выбор — значения q=232−5 или q=264−59, для которых тоже существуют быстрые алгоритмы взятия остатка от деления на q (при этом диапазон допустимых значений x немного сужают)Шаблон:Sfn. Частое заблуждение — полагать q=232. Существуют семейства строк, на которых хеш с q=2L будет всегда давать множество коллизий, независимо от выбора L.Шаблон:Sfn Эти и другие дальнейшие детали реализации и теоретического анализ полиномиального хеша можно найти в статье об алгоритме Рабина — Карпа.

Полиномиальный хеш над полем GF(2L)

Данный хеш похож на обычный полиномиальный хеш, но все вычисления в нём производятся в конечном поле GF(2L). Обычно L выбирается равным 64. Элементы поля — это числа 0,1,…,2L−1. Сложение в поле реализуется с помощью операции побитового исключающего «или» ⊕, а умножение выполняется с помощью операции a⋆b, которая сначала Шаблон:Iw a на b, а потом берёт остаток от «беспереносного» деления результата на некоторый выбранный фиксированный элемент q∈{2L,2L+1,…,2L+1−1} (беспереносным делением здесь названа операция, обратная беспереносному умножению). Элемент q=2i1+2i2+⋯+2ik должен быть выбран так, что L=i1>i2>⋯>ik≥0 и xi1+xi2+⋯+xi0 — это неприводимый многочлен над полем GF(2) (на поле GF(2L) часто смотрят как на множество многочленов над полем GF(2) по модулю произвольного неприводимого многочлена степени L). Например, можно положить q=264+24+23+2+1Шаблон:Sfn. Тогда хеш вычисляется следующим образомШаблон:Sfn:

h(a1a2⋯an)=(a1⋆xn−1)⊕(a2⋆xn−2)⊕⋯⊕(an−1⋆x)⊕an,

где x — это случайно выбранное на этапе инициализации хеша число из диапазона {0,1,…,2L−1}, а xm — это короткая запись для x⋆x⋆⋯⋆x, где x повторён m раз. С помощью основной теоремы алгебры можно показать, что вероятность коллизии хешей двух различных строк длины n не превосходит n/2L. ПоказаноШаблон:Sfn, что на современных процессорах Intel и AMD вся необходимая для хеша арифметика над полем GF(2L) может быть эффективно вычислена с помощью инструкций из расширения Шаблон:Iw.

Хеш циклическими полиномами (Buzhash)

Пусть h′ — какой-то хеш, который отображает символы a1,…,an хешируемой строки в L-битовые числа (обычно L=32 или L=64). Хеш циклическими полиномами определяется следующим образомШаблон:Sfn:

h(a1a2⋯an)=sn−1(h′(a1))⊕sn−2(h′(a2))⊕⋯⊕s(h′(an−1))⊕h′(an),

где ⊕ — это операция побитового исключающего «или», а si(x) — это операция циклического сдвига L-битового числа x на i битов влево. Несложно показать, что данный хеш кольцевой:

h(a2a3…an+1)=s(h(a1a2…an))⊕sn(h′(a1))⊕h′(an+1).

Главное преимущество этого хеша в том, что он использует только быстрые побитовые операции доступные на многих современных компьютерах. Качество хеша напрямую зависит от выбора функции h′. Лемире и КасерШаблон:Sfn доказали, что если функция h′ выбирается случайно из семейства Шаблон:Iw, то вероятность совпадения хешей двух различных строк длины n не превосходит 1/2L−n+1. Это накладывает определённые ограничения на диапазон задач, в которых данный хеш может использоваться. Во-первых, длина хешируемых строк должна быть меньше L. Для алгоритмов хеширования общего назначения это условие может быть проблемой, но, например, для хеширования n-грамм, где n обычно не превосходит 16, такое ограничение является естественным (в случае n-грамм роль символов играют отдельные лексемы текста). Во-вторых, выбор семейства независимых функций h′ в некоторых случаях тоже может быть проблемой. Для байтового алфавита свойством независимости обладает семейство функций h′, закодированных таблицей из 256-и различных случайных L-битовых чисел (выбор функции — это заполнение таблицы). Для хеширования n-грамм можно присваивать различные случайные L-битовые числа различным лексемам (обычно число разных лексем в таких задачах относительно невелико) и такое семейство хеш-функций h′ тоже имеет свойство независимости.

Хеш Рабина

Данный хеш применим только в специальном случае, когда символы хешируемой строки a1a2⋯an суть числа 0 и 1. Идея хеша в том, чтобы смотреть на входную строку a1a2⋯an как на многочлен A(x)=a1xn−1⊕a2xn−2⊕⋯⊕an−1x⊕anx0 над полем GF(2), а сам хеш представляет собой взятие остатка от деления A(x) на случайно выбранный на этапе инициализации хеша неприводимый многочлен P(x) степени L над полем GF(2). По существу это та же процедура, что используется в CRC. Рассмотрим её более подробно.

Результат хеширования строки a1a2⋯an — это последовательность битов bL−1bL−2⋯b0. Число L выбирается простымШаблон:Sfn и достаточно большим, но так чтобы последовательность bL−1bL−2⋯b0 умещалась в одно машинное слово (обычно берут L=31 или L=61Шаблон:Sfn). Пусть P(x)=pLxL⊕pL−1xL−1⊕⋯⊕p1x⊕p0 представляет собой некоторый неприводимый многочлен степени L над полем GF(2). Обозначим через p соответствующее число с битовым представлением pLpL−1⋯p0. Хеш-функция h(a1a2⋯an) определяется как число с битовым представлением bL−1bL−2⋯b0, таким что многочлен B(x)=bL−1xL−1⊕bL−2xL−2⊕⋯⊕b1x⊕b0 является остатком от деления многочлена A(x)=a1xn−1⊕a2xn−2⊕⋯⊕an−1x⊕an на многочлен P(x), то есть B(x)=A(x)modP(x).

Несмотря на весьма запутанное определение, хеш Рабина довольно просто реализуем (если неприводимый многочлен P(x) уже найден). Вычисления опираются на такое несложное наблюдение: если число b с битовым представлением bL−1bL−2⋯b0 кодирует многочлен B(x)=bL−1xL−1⊕bL−2xL−2⊕⋯⊕b1x⊕b0, то число sh(b) кодирует многочлен x⋅B(x), где sh(b) обозначает операцию побитового сдвига числа b на один бит влево с замещением младшего бита нулём (не путать с циклическим сдвигом s, определённым выше!). Пусть b=h(a1a2⋯ai), и bL−1bL−2⋯b0 — это битовое представление b. Тогда h(a1a2⋯aiai+1) вычисляется следующим образом:

sh(b)⊕ai+1, если bL−1=0,
sh(b)⊕p⊕ai+1, если bL−1=1.

Хеш является кольцевым. Пусть b=h(a1a2⋯an) и bL−1bL−2⋯b0 — это битовое представление b. Хеш h(a2a3⋯anan+1) вычисляется следующим образомШаблон:Sfn:

sh(b)⊕an⊕(a1⋅c), если bL−1=0,
sh(b)⊕p⊕an⊕(a1⋅c), если bL−1=1,

где c — это L-битовое число, битовое представление которого соответствует многочлену xnmodP(x). Число c вычисляют заранее при инициализации хеша строки длины n.

Главная сложность — случайным образом выбрать неприводимый многочлен P(x) степени L. РабинШаблон:Sfn описал эффективный алгоритм, позволяющий это сделать, и доказал, что вероятность коллизии хешей двух различных строк длины n при случайном выборе P(x) не превосходит n/2L.

Отметим, что данный хеш часто путают с полиномиальным хешем из-за схожей области применения, рассмотрения многочленов и общего автора.

Ссылки

Примечания

Шаблон:Примечания

Литература