Как быстро выполнить поиск в списке из 40 миллионов слов?

#c# #list #dictionary

#c# #Список #словарь

Вопрос:

Как быстро выполнить поиск в списке, который содержит 40 миллионов слов?

Мне нужно найти слова, содержащие как минимум 4 буквы, которые я указал перед продолжением.

Пример: в списке всего несколько слов:

 dogging
dopping
baobabisaneviltree
  

Мои конкретные буквы в строковом формате ‘odxxini’. Мне нужно найти любые слова, содержащие любые (4 ) символы из моей строки.

Результат:

 dopping
dogging
  

(потому что оба слова содержат ‘o’ ‘d’ ‘i ‘ ‘n’)
Надеюсь, я хорошо объяснил. Извините за английский. Пожалуйста, исправьте ошибки.

Если у кого-нибудь есть какие-либо знания об этой проблеме, я буду рад его услышать. 🙂

Я написал до сих пор (потому что это начало ..) этот код:

 private void seeksearcher()
        {
            double counter = 0, k=0;
            double licznik = (double)listwords.Capacity;

            char[] letterarray = stringletters.ToCharArray();
            foreach(String word in listwords)
            {

                for(int i=0;i<letterarray.Length;i  )
                    if(word.Contains(letterarray[i]))
                        counter  ;
                if(counter > 4)
                    textBox2.Text =word   Environment.NewLine;

            }
        }
  

Я почти уверен, что сложность сейчас n * 7n, она ужасно большая : (

Комментарии:

1. создание (и использование) индекса по вашему словарю ускорит ваш поиск

2. Будет ли строка «iixx» соответствовать примеру? То есть означает ли включение «x» дважды, что два «x» в искомой строке будут считаться двумя совпадающими буквами?

3. Я полагаю, что ваша сложность больше, n * m где n — длина списка, а m — длина строки поиска.

4. @Jeffrey L. Whitledge если у меня есть два xx в моей конкретной строке (конечно, эта строка, которую я сократил до массива символов), это означает, что в словах «can» есть два x’es, однако я хотел бы найти слова, которые содержат как можно больше букв из моей конкретной строки. Каждая буква, которая использовалась, больше не будет использоваться в этом слове.

5. @Michal, нет, я имею в виду, что это было бы примерно 40 000 000 * 8, используя гипотетические длины вашего списка слов и строки поиска. На самом деле, знаете что, это больше n * m * k , длина списка * длина слова * длина строки поиска. Но я могу ошибаться.

Ответ №1:

Во-первых, очевидно, что ни одно решение не является более быстрым, чем размер набора решений. Если у вас случайно есть строка поиска, которая соответствует каждому слову в словаре, то для перечисления набора решений требуется перечисление словаря.

Давайте предположим, что размер каждого набора решений очень мал по сравнению с размером словаря.

Давайте также предположим, что размер каждой записи в словаре невелик; у вас там нет слов из десяти тысяч букв или чего-то глупого в этом роде.

Учитывая эти два ограничения, большой вопрос в том, требуется ли вам сублинейное время поиска?

Алгоритмы линейного времени просты. Например:

  • отсортируйте символы каждого слова из лексикона в алфавитном порядке.
  • отсортируйте символы запроса в алфавитном порядке
  • выполните сравнение последовательности отсортированного запроса с каждым словом отсортированного словаря.

То есть, предположим, у вас есть лексикон

 STOPPING
POTSHARD
OPTING
DECORATE
  

и запрос TOPSXZ . Отсортируйте запрос по символам: OPSTXZ . Теперь просмотрите словарь, отсортировав его по символам:

 STOPPING --> GINOPPST
POTSHARD --> ADHOPRST
OPTING   --> GINOPT
DECORATE --> ACDEEORT
  

И теперь легко определить, есть ли у вас четыре или более совпадений; вы просто запускаете алгоритм самой длинной общей подпоследовательности в OPSTXZ и GINOPPST и обнаруживаете, что самой длинной общей подпоследовательностью является OPST , состоящая из четырех букв, так что она совпадает. Самая длинная распространенная подпоследовательность OPSTXZ и ADHOPRST также OPST , поэтому она обрабатывается. Самая длинная общая подпоследовательность OPSTXY и GINOPT равна OPT , то есть всего трем, а самая длинная общая подпоследовательность OPSTXY и ACDEEORT OT , то есть всего двум.

Предполагая, что все слова короткие, мы знаем, что проблема с самой длинной общей подпоследовательностью и проблема сортировки по куче символов могут быть решены быстро. Вам просто нужно проделать это 40 миллионов раз, и все готово.

Теперь, если вам нужно сублинейное решение, при котором вы заранее исключаете из рассмотрения кучу этих 40 миллионов слов из лексикона, это будет сложнее. Вам требуется сублинейное решение?

Комментарии:

1. Сложнее, но интереснее! 😀

Ответ №2:

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

Например, у вас могут быть указатели на все слова, содержащие букву d, затем указатели на все слова, содержащие букву o. И т.д. Тогда вы получите гораздо меньший список, который вам будет легче искать, найдя пересечение ваших букв (слов, в которых есть все нужные вам буквы).

Конечно, это просто перемешивает работу, заставляя ее выполнять большую обработку заранее, а не во время поиска.

Комментарии:

1. редактировать: как насчет запуска отдельного потока для каждого буквенного индекса?

2. Конечно, вы можете выполнять многопоточную обработку. Вы могли бы рассмотреть возможность использования расширений Parlell для этого.

Ответ №3:

Можете ли вы заранее проиндексировать слова? Я бы начал с индексации списка слов, создав отсортированный список слов для каждого символа:

 a: baobabisaneviltree
b: baobabisaneviltree
c: 
d: dogging, dopping
e: etc
  

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

 dogging: 4
dopping: 4
dapper: 1
  

Затем я бы прошелся по словарю в поисках чисел, превышающих 4.

Если вы не можете проиндексировать, то ваше решение настолько хорошо, насколько вы можете получить. Вам обязательно нужно посмотреть на каждую букву в каждом слове (O (n * m)), чтобы увидеть, появляется ли данная буква в слове, затем вам нужно проверить наличие каждой буквы. Одна из проблем с вашим решением заключается в том, что вы будете добавлять слово в текстовое поле несколько раз, возможно, вам захочется сделать это if(counter == 4) .


Забавный код (непроверенный):

 // With 40 million words this can use a lot of space.  You would probably
// want to create the index on disk and maybe the intermediate processing
// as well.
var index = wordList.SelectMany(word => word.ToCharArray(), 
                                (word, character) => 
                                  new { word, character})
                    .ToLookup(x => x.character, x => x.word);
var result = letterArray.Distinct()
                        .SelectMany(c => index[c])
                        .GroupBy(word => word)
                        .Where(word => word.Count() > 4)
                        .Select(word => word.Key);