mirror of
https://github.com/ClickHouse/ClickHouse.git
synced 2024-11-24 00:22:29 +00:00
117 lines
5.9 KiB
C
117 lines
5.9 KiB
C
|
#pragma once
|
|||
|
|
|||
|
#include <stdint.h>
|
|||
|
#include <string.h>
|
|||
|
#include <algorithm>
|
|||
|
|
|||
|
|
|||
|
/** Поиск подстроки в строке по алгоритму Вольницкого:
|
|||
|
* 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<offset_t>::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<const ngram_t *>(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<const char *>(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<const ngram_t *>(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<const unsigned char *>(search(reinterpret_cast<const char *>(haystack), haystack_size));
|
|||
|
}
|
|||
|
};
|