#pragma once #include #include #include /** Поиск подстроки в строке по алгоритму Вольницкого: * http://volnitsky.com/project/str_search/ * * haystack и needle могут содержать нулевые байты. * * Алгоритм: * - при слишком маленьком или слишком большом размере needle, или слишком маленьком haystack, используем std::search или memchr; * - при инициализации, заполняем open-addressing linear probing хэш-таблицу вида: * хэш от биграммы из needle -> позиция этой биграммы в needle + 1. * (прибавлена единица только чтобы отличить смещение ноль от пустой ячейки) * - в хэш-таблице ключи не хранятся, хранятся только значения; * - биграммы могут быть вставлены несколько раз, если они встречаются в needle несколько раз; * - при поиске, берём из haystack биграмму, которая должна соответствовать последней биграмме needle (сравниваем с конца); * - ищем её в хэш-таблице, если нашли - достаём смещение из хэш-таблицы и сравниваем строку побайтово; * - если сравнить не получилось - проверяем следующую ячейку хэш-таблицы из цепочки разрешения коллизий; * - если не нашли, пропускаем в haystack почти размер needle байт; * * Используется невыровненный доступ к памяти. */ class Volnitsky { private: typedef uint8_t offset_t; /// Смещение в needle. Для основного алгоритма, длина needle не должна быть больше 255. typedef uint16_t ngram_t; /// n-грамма (2 байта). const char * needle; size_t needle_size; const char * needle_end; size_t step; /// Насколько двигаемся, если n-грамма из haystack не нашлась в хэш-таблице. static const size_t hash_size = 64 * 1024; /// Обычно помещается в L1-кэш, хотя занимает его целиком. offset_t hash[hash_size]; /// Хэш-таблица. bool fallback; /// Нужно ли использовать fallback алгоритм. public: /** haystack_size_hint - ожидаемый суммарный размер haystack при вызовах search. Можно не указывать. * Если указать его достаточно маленьким, то будет использован fallback алгоритм, * так как считается, что тратить время на инициализацию хэш-таблицы не имеет смысла. */ Volnitsky(const char * needle_, size_t needle_size_, size_t haystack_size_hint = 0) : needle(needle_), needle_size(needle_size_), needle_end(needle + needle_size), step(needle_size - sizeof(ngram_t) + 1) { if (needle_size < 2 * sizeof(ngram_t) || needle_size >= std::numeric_limits::max() || (haystack_size_hint && haystack_size_hint < 20000)) { fallback = true; return; } else fallback = false; memset(hash, 0, hash_size * sizeof(hash[0])); for (int i = needle_size - sizeof(ngram_t); i >= 0; --i) { /// Кладём смещение для n-грама в соответствующую ему ячейку или ближайшую свободную. size_t cell_num = *reinterpret_cast(needle + i) % hash_size; while (hash[cell_num]) cell_num = (cell_num + 1) % hash_size; /// Поиск следующей свободной ячейки. hash[cell_num] = i + 1; } } /// Если не найдено - возвращается конец haystack. const char * search(const char * haystack, size_t haystack_size) const { const char * haystack_end = haystack + haystack_size; if (needle_size == 1) { const char * res = reinterpret_cast(memchr(haystack, needle[0], haystack_size)); return res ? res : haystack_end; } if (fallback || haystack_size <= needle_size) { /// Как ни странно, std::search работает намного быстрее memmem из eglibc. return std::search(haystack, haystack_end, needle, needle_end); } /// Будем "прикладывать" needle к haystack и сравнивать n-грам из конца needle. const char * pos = haystack + needle_size - sizeof(ngram_t); for (; pos <= haystack_end - needle_size; pos += step) { /// Смотрим все ячейки хэш-таблицы, которые могут соответствовать n-граму из haystack. for (size_t cell_num = *reinterpret_cast(pos) % hash_size; hash[cell_num]; cell_num = (cell_num + 1) % hash_size) { /// Когда нашли - сравниваем побайтово, используя смещение из хэш-таблицы. const char * res = pos - (hash[cell_num] - 1); for (size_t i = 0; i < needle_size; ++i) if (res[i] != needle[i]) goto next_hash_cell; return res; next_hash_cell:; } } /// Оставшийся хвостик. return std::search(pos - step + 1, haystack_end, needle, needle_end); } const unsigned char * search(const unsigned char * haystack, size_t haystack_size) const { return reinterpret_cast(search(reinterpret_cast(haystack), haystack_size)); } };