Как можно повысить скорость поиска подстроки в массиве строк?

Какую структуру данных можно использовать для ускорения поиска элементов по подстроке в статичном массиве из 25 миллионов строк разной длины ([A-Z0-9А-Я\s]+), если время выполнения For + string.Contains составляет в среднем 1110 мс, а For + Regex.IsMatch - 3410 мс?
  • 7 февраля 2024 г. 6:18
Ответы на вопрос 3
Для ускорения поиска подстрок в большом массиве строк можно использовать несколько подходов. Один из них - использование специализированных индексов и алгоритмов. Рассмотрим несколько популярных структур данных и алгоритмов:

1. **Префиксное дерево (Trie)** - это дерево, где каждый путь от корня до узла соответствует префиксу какой-либо строки. Префиксное дерево подходит для поиска строк, которые начинаются с определённой подстроки, но оно может быть неэффективно для поиска произвольной подстроки внутри строки.

2. **Суффиксное дерево** - это чрезвычайно эффективная структура данных для поиска подстрок, которая позволяет выполнять поиск за время, пропорциональное длине подстроки, но построение суффиксного дерева может быть ресурсоёмким.

3. **Суффиксный массив** с LCP (Longest Common Prefix) - это более компактная альтернатива суффиксному дереву с быстрым поиском подстрок. Его построение также занимает время, но он эффективен для выполнения многократных поисков.

4. **Алгоритм Кнута-Морриса-Пратта (KMP)** - является классическим алгоритмом поиска подстроки в строке, который избегает повторного изучения символов строки. 

5. **Алгоритм Бойера-Мура** - это эффективный алгоритм поиска строк, который пропускает секции данных на основе таблицы сдвигов.

6. **Full-text search engine** - Наконец, можно использовать полноценную поисковую систему, такую как ElasticSearch или Apache Solr, которые оптимизированы для быстрого поиска по тексту. Они поддерживают индексацию больших массивов данных и предоставляют различные возможности для оптимизированного поиска.

Для статичного массива из 25 миллионов строк, скорее всего, наиболее подходящими будут суффиксный массив с LCP или полноценная поисковая система, в зависимости от специфических условий и требований производительности. Обе эти структуры данных позволят существенно уменьшить время поиска по сравнению с простыми циклами и методами `Contains` или `Regex.IsMatch`. Однако, их реализация и поддержание также потребуют дополнительных усилий.
Алгоритм Ахо-Корасика. Структура там будет бор (trie) с префиксными ссылками, с привкусом конечного автомата. Работать будет примерно в 10 миллионов раз быстрее.
Раз нет ограничений по памяти и надо максимально быстро, то можно разложить весь массив в дерево таблиц переходов с шагом в один символ на таблицу. Самый быстрый и самый затратный по памяти. Таблица на каждый символ - 256 байт. Скорость поиска зависит только от размера строки и не зависит от объема данных: один символ - один переход в таблице к следующей таблице или конец поиска, если ноль. Я так уже делал: использовать имеет смысл на объемах примерно от 4 гигабайт данных (чем больше объем - тем меньше затраты на каждый символ). Но, если память позволяет и цель именно в скорости - то вполне нормальная плата памятью за скорость. Можно сократить расход памяти, если перекодировать строки в кодировку по числу используемых символов. Тогда таблица переходов будет в несколько раз меньше. Более экономный и более медленный вариант - дерево массивов/списков с шагом в 2/4/8 символов, при этом в поиске сравнение не по символам делать, а сразу по 2/4/8 байт: т.е., работаем со строкой как с массивом байтов и получаем оттуда uint16/uint32/uint64 и их и сравниваем, ибо процессору все равно какую инструкцию выполнять - один байт сравнить или 8. Возможно, конечно, оптимизатор в поиске по строке это все и оптимизирует. Я давно уже не смотрю на результаты его работы - так что тут только опытным путем. Ну и щас еще других вариантов накидают с деревьями тоже.
Похожие вопросы