Какие алгоритмы используются для поиска подслов в словах?

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

В словаре имеем:

dada, dadaism, daddled, daddling

Результат:

a, aism, dled, dling

У меня есть словарь со всеми словами, поэтому мне нужен только алгоритм. Кто-то предложил мне использовать алгоритм Патриции, но я не смог найти ни одного образца для С#. Мой словарь очень большой, поэтому мне нужно найти очень быстрый алгоритм.


More information:

  • Словарь отсортирован.

  • person deadfish    schedule 10.12.2011    source источник


    Ответы (6)


    То, как вы это сделаете, будет зависеть от того, как устроен ваш словарь. Если это отсортированный список слов, вы можете использовать двоичный поиск, чтобы найти первое слово, начинающееся с «папа», а затем просмотреть только те, которые используют StartsWith и Substring. То есть:

    List<string> Words = LoadWords(); // however you load them
    Words.Sort();
    
    // Now, search for "dad" (or whatever)
    string prefix = "dad";
    
    int index = Words.BinarySearch(prefix);
    
    // If the returned index is negative, the word wasn't found.
    // The index is the one's compliment of the the place where it would be in the list.
    if (index < 0)
    {
        index = ~index;
    }
    
    for (int i = index; i < Count && Words[i].StartsWith(prefix))
    {
        Console.WriteLine(Words[i].Substring(prefix.Length));
    }
    

    Это должно быть очень быстро. Сортировка является единовременной стоимостью после загрузки. А можно и вовсе исключить, если хранить словарь в отсортированном порядке. Бинарный поиск — O(log n), где n — количество слов в словаре.

    Если ваш словарь неупорядочен, то вам придется просмотреть все слова, что займет много времени.

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

    person Jim Mischel    schedule 10.12.2011
    comment
    +1 нравится, как простейшая структура данных обеспечивает (имо) лучшее решение. - person Nicolas78; 10.12.2011
    comment
    @ Nicolas78: Это часто бывает, когда требования просты. Ситуация немного меняется, когда требования становятся более сложными. Представьте, например, что вы хотите найти все слова, содержащие подстроку папа. - person Jim Mischel; 10.12.2011

    Это звучит как идеальное использование для Trie/DAWG (направленный ациклический граф слов). Я понимаю, что дерево Патрисии - это разновидность дерева. Здесь есть хорошая статья о попытках и их реализации.

    person spender    schedule 10.12.2011
    comment
    О, я забыл про попытки :)), но создание trie в случае доступного списка нехорошо, также если нас просто интересуют StartsWith или EndsWith (как сказано в вопросе) и мы разрешили создать trie, лучше создать отсортированный список . - person Saeed Amiri; 10.12.2011
    comment
    @Eric Lippert немного обсуждает попытки в этом ответе: net/8329927#8329927" title="как мне быстро найти самую длинную совпадающую строку в c net"> stackoverflow.com/questions/8326446/ - person spender; 10.12.2011
    comment
    Я знаю, что такое trie :) Я реализовал один из них около 8 лет назад :), но на самом деле это было недостаточно быстро, может быть, у моей реализации есть проблема или другие проблемы, в любом случае, здесь задан вопрос о поиске всех элементов со специальным соответствием строк (например, StartsWith) не для поиска точной строки, может быть, этот шаблон сопоставления строк предназначен не только для startwith, в этом случае я не вижу, чем полезен trie. - person Saeed Amiri; 10.12.2011

    Циклы for могут быть довольно быстрыми для очень маленьких словарей. Но если у вас есть совпадающие наборы из тысяч слов, это будет очень медленно. Предполагая, что ваш словарь отсортирован (если не отсортирован), вы можете использовать функцию BinarySearch, чтобы найти первый и последний элементы диапазона, а затем использовать цикл for для создания результатов.

    Чтобы быть более практичным, у меня есть (отсортированный) словарь с 354984 словами, включая эти 35 слов, начинающихся с папы: папа, папа, дада, дадаизм, дадаизмы, дадаист, дадаист, дадаистически, дадаисты, дадап, дадас, папа сгорел, папа, папы , папа, даддл, даддл, даддл, даддлинг, папа, папа, папа, папа, папа, папочка, папа, даденхудд, дадинг, дадо, дадоед, дадо, дадоин, дадо, дадушо, папы и дадучус. Если я буду следовать подходу Джима, мне придется выполнить 35 "StartsWith", что нормально. В случае префикса "sat" у меня 228 слов, а в случае префикса "cat" у меня 692 слова. Для размера моего словаря мне нужно в общей сложности 40 сравнений строк (в худшем случае), чтобы найти первый и последний элементы.

    Если вы хотите использовать любую реализацию trie, убедитесь, что она поддерживает как минимум числа и тире, если ваш словарь включает такие записи, как 1st или в реальном времени.

    person Community    schedule 10.12.2011

    Самый известный, который я знаю, это "knuth morris pratt string алгоритм сопоставления".

    Если вы посмотрите на ссылку, есть некоторые другие, такие как алгоритм поиска строки Бойера-Мура, ... Это общие алгоритмы, но если вас интересуют особые случаи, такие как начало, ... в большинстве случаев языки имеют это случаях, например, в C# вы можете использовать StartsWith, EndsWith, нет необходимости реализовать их снова.

    person Saeed Amiri    schedule 10.12.2011
    comment
    @Saaed KMP лучше всего подходит для поиска большой отдельной строки, а не списка строк. - person spender; 10.12.2011
    comment
    Да, KMP предназначен для поиска по шаблону, но в этом случае, если у нас есть отсортированный список, мы можем использовать этот поиск по шаблону, и он работает быстро, и если у нас снова нет отсортированного списка, это может помочь для более быстрого поиска, но я не Другого способа я не знаю, попробуйте, как и другие, предназначенные для поиска полного слова, а не специального шаблона. - person Saeed Amiri; 10.12.2011

    если вам нужно что-то простое, вы можете попробовать это:

            string[] dict = new string[] { "dada", "dadaism", "daddled", "daddling" };
            string prefix = "dad";
    
            var words = from d in dict
                        where d.StartsWith(prefix)
                        select d.Substring(prefix.Length);
    
    person wolfovercats    schedule 10.12.2011
    comment
    Бьюсь об заклад, простой цикл for быстрее этого :) - person Saeed Amiri; 10.12.2011

    Вы можете использовать TRIE для этого. Полную реализацию и руководство можно найти здесь.

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

    person bragboy    schedule 10.12.2011
    comment
    В BS я также реализовал trie, но на самом деле это было недостаточно быстро в действии. Ваша попытка достаточно быстра? вы измеряли это? Я реализовал trie на С#, и это было быстро, если мы хотим найти очень-очень большой документ, но для данных, которые меньше 2 мегабайт, это было не так быстро. - person Saeed Amiri; 10.12.2011
    comment
    @SaeedAmiri: я не сравнивал его, но вы можете увидеть пример пользовательского интерфейса по ссылке, и он очень быстро извлекает слова. Единственными накладными расходами для TRIE, конечно же, является его создание. Но после этого он будет реактивным. В этом нет сомнений. Большой O говорит сам за себя в практическом случае TRIE. - person bragboy; 10.12.2011