Однозначная хеш-функция для строки длиной 76 символов

Вот моя проблема (я программирую на C):

У меня есть несколько огромных текстовых файлов, содержащих последовательности ДНК (каждый файл имеет примерно 65 миллионов строк и размер около 4 ~ 5 ГБ). В этих файлах много дубликатов (пока не знаю, сколько их, но их должно быть много миллионов), и я хочу вернуть на выходе файл только с различными значениями. Каждая строка имеет связанное значение качества, поэтому, если, например, у меня есть 5 одинаковых строк с разными значениями качества, я выберу лучшую и отброшу остальные 4.

Снижение требований к памяти и повышение эффективности скорости, насколько я могу, жизненно важно. Моя идея заключалась в том, чтобы создать массив JudyHS с использованием хэш-функции, чтобы преобразовать последовательность ДНК String (которая состоит из 76 букв и имеет 7 возможных символов) в целое число, чтобы уменьшить использование памяти (4 или 8 байтов вместо 76 байтов на многих миллионы записей должно быть настоящим достижением). Таким образом, я мог использовать целое число в качестве индекса и хранить только лучшее значение качества для этого индекса. Проблема в том, что я не могу найти хэш-функцию, которая УНИВЕРСАЛЬНО определяет такую ​​длинную строку и выдает значение, которое может быть сохранено внутри целого числа или даже long long!

Моей первой идеей для хеш-функции было что-то вроде хеш-функции строки по умолчанию в Java: s [0] * 31 ^ (n-1) + s [1] * 31 ^ (n-2) + ... + s [ n-1], но я смог получить максимальное значение 8,52 * 10 ^ 59 .. слишком велико. Как насчет того, чтобы сделать то же самое и сохранить это в дубле? Становятся ли вычисления намного медленнее? Обратите внимание, что мне нужен способ УНИВОКАЛЬНО определить строку, избегая коллизий (или, по крайней мере, они должны быть крайне редкими, потому что мне пришлось бы обращаться к диску при каждом столкновении, довольно дорогостоящая операция ...)


person Alex    schedule 03.05.2011    source источник
comment
Не отвечу на ваш вопрос, но надеясь решить вашу проблему: будет ли дерево префиксов подходящими данными структура для компактного хранения ваших данных?   -  person Robᵩ    schedule 03.05.2011
comment
Спасибо за ответ, но из того, что я понял, это практически то, что представляет собой массив Джуди, и, в любом случае, я читал утверждения, что для них это более эффективно, поэтому я хочу попробовать   -  person Alex    schedule 03.05.2011


Ответы (2)


У вас есть 7 ^ 76 возможных последовательностей ДНК и вы хотите сопоставить их с 2 ^ 32 хэшами без коллизий? Невозможно.

Для этого вам понадобится минимум log2 (7 ^ 76) = 214 бит, около 27 байт.

Если вы можете жить с некоторыми коллизиями, я бы рекомендовал придерживаться CRC32 или md5 вместо того, чтобы снова изобретать новое колесо.

person Gunther Piez    schedule 03.05.2011
comment
Есть ли какой-нибудь алгоритм, который позволяет мне закодировать эти 7 ^ 76 возможных значений в этих 214 битах? - person Alex; 03.05.2011
comment
@Alex: Без специальной арифметики с длинными целыми числами я бы использовал группы из 7 ^ 11, которые можно кодировать менее чем 32-битными, и использовал бы 7 из этих 32-битных целых чисел. Предполагая, что значения в ваших последовательностях находятся в диапазоне 0..6: для каждой группы из 11 значений вычислите (((v0 * 7 + v1) * 7 + v2) * 7 + v3) * 7 ... + v10. Это будет меньше 1977326743 и поместится в 32-битное целое число. Вычислите это для 7 групп, установив v10 в последней группе равным нулю. - person Gunther Piez; 04.05.2011
comment
Но я бы предпочел использовать простую хеш-функцию и достаточно длинный ключ. Как писал Томас, простое использование 64-битного хеша сделает коллизии очень маловероятными. Поиск en.wikipedia.org/wiki/Birthday_problem: вероятность столкновения в таблице менее 1/1000 в вашем случае. - person Gunther Piez; 04.05.2011
comment
Проблема с коллизиями заключается в том, что есть риск отбросить некоторые значения, которые не следует отбрасывать, и мне не разрешено отбрасывать даже одну строку ради эффективности. Думаю, я воплощу вашу идею о 7 группах и добавлю возможность также использовать более эффективное (но немного небезопасное) предложение Томаса. Спасибо вам обоим! - person Alex; 04.05.2011
comment
Обратите внимание, что вероятность коллизии составляет не 1/1000 на доступ к строке, а скорее во всей таблице из 65 миллионов записей. Или, говоря наоборот: если у вас есть 1000 таблиц по 5 ГБ каждая, что составляет 5 ТБ данных, скорее всего, только в одной из этих таблиц произойдет коллизия, в остальных 999 коллизиях не будет. - person Gunther Piez; 04.05.2011

«Простой» способ получить хэш-функцию без коллизий для N элементов - это использовать хорошую функцию смешивания (скажем, криптографическую хеш-функцию) и обрезать размер, чтобы хеш-результаты оставались живыми. в пространстве размером не менее N 2. Здесь у вас 65 миллионов строк - это умещается на 26 битах (2 26 близко к 65 миллионам), поэтому 52 бита «должно быть достаточно».

Вы можете попробовать использовать быструю криптографическую хеш-функцию, даже «сломанную», поскольку это не проблема безопасности. MD4, MD5, SHA-1 ... затем усеките результат до первых (или последних) 64 бит, сохраните его в 64-битном целочисленном типе. Скорее всего, у вас не будет какой-либо коллизии среди ваших 65 миллионов строк; и если вы их получите, они будут очень редкими.

Для оптимизированных реализаций C хэш-функций найдите sphlib. Используйте предоставленную функцию sph_dec64le(), чтобы «декодировать» последовательность из 8 бит в 64-битовое целое число без знака.

person Thomas Pornin    schedule 03.05.2011
comment
Проблема в том, что каждый раз, когда я получаю столкновение, мне нужно различать, вызвано ли оно дубликатом (и поэтому отбрасывать его) или другим значением (и сохранять его) без сохранения исходной строки в хеш-таблице. Если бы существовал какой-то алгоритм, который позволяет мне это делать (но я не могу представить, как), количество столкновений было бы почти несущественным .. - person Alex; 03.05.2011