Поиск анаграмм для данного слова

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

пример : Anagram & Nagaram являются анаграммами (без учета регистра).

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

1) Sort строки и сравнить их.

2) создать frequency map для этих строк и проверьте, являются ли они то же самое или нет.

но в этом случае нам дается слово (для простоты допустим только одно слово, и оно будет иметь только одно слово анаграммы), и для этого нам нужно найти анаграммы.

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

Итак, какие альтернативы у нас есть здесь ?

я тоже читал в похожей теме, Что что-то можно сделать с помощью Tries но человек не объяснил, что такое алгоритм и почему мы использовали Trie в первую очередь, просто реализация была предоставлена в Python или Ruby. Так что это было не очень полезно, поэтому я создал этот новый поток. Если кто-то хочет поделиться своей реализацией (кроме C,C++ или Java), пожалуйста, объясните это тоже.

12 ответов


пример алгоритма:

Open dictionary
Create empty hashmap H
For each word in dictionary:
  Create a key that is the word's letters sorted alphabetically (and forced to one case)
  Add the word to the list of words accessed by the hash key in H

чтобы проверить все анаграммы данного слова:

Create a key that is the letters of the word, sorted (and forced to one case)
Look up that key in H
You now have a list of all anagrams

сравнительно быстро построить, невероятно быстро посмотреть вверх.


Я придумал новое решение, наверное. Он использует фундаментальную теорему арифметики. Поэтому идея состоит в том, чтобы использовать массив из первых 26 простых чисел. Затем для каждой буквы во входном слове получаем соответствующее простое число A = 2, B = 3, C = 5, D = 7 ... а затем вычисляем произведение нашего входного слова. Затем мы делаем это для каждого слова в словаре, и если слово соответствует нашему входному слову, мы добавляем его в результирующий список. Все анаграммы будут иметь одинаковую подпись, потому что

любое целое число больше 1 является простым числом или может быть записано как уникальное произведение простых чисел (игнорирование порядка).

вот код. Я преобразую слово в верхний регистр, а 65-это позиция A, которая соответствует моему первому простому числу:

private int[] PRIMES = new int[] { 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31,
        37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103,
        107, 109, 113 };

этот метод:

 private long calculateProduct(char[] letters) {
    long result = 1L;
    for (char c : letters) {
        if (c < 65) {
            return -1;
        }
        int pos = c - 65;
        result *= PRIMES[pos];
    }
    return result;
}

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

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

если каждая позиция буквы является измерением, и значение в этом измерении основано на букве (скажем, код ASCII). Затем вы можете рассчитать длину слова вектор.

например, скажите ' A ' =65,'B' =66, затем length("AB") = sqrt(65*65 + 66*66). Очевидно,length("AB") = length("BA").

понятно, что если два слова являются анаграммами, то их векторы имеют одинаковую длину. Следующий вопрос: если два вектора слов (одинакового количества букв) имеют одинаковую длину, являются ли они анаграммами? Интуитивно я бы сказал нет, поскольку все векторы с такой длиной образуют сферу, их много. Не уверен, так как в этом случае мы находимся в целочисленном пространстве, сколько их на самом деле.

но по крайней мере, это позволяет вам разбить свой словарь еще дальше. Для каждого слова в словаре вычислите расстояние вектора: for(each letter c) { distance += c*c }; distance = sqrt(distance);

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

вы создадите карту для каждого расстояния.

затем ваш поиск становится следующим алгоритмом:

  1. использовать правильная карта словаря, основанная на длине слова
  2. вычислить длину вектора
  3. Поиск списка слов, которые соответствуют этой длине
  4. пройдите по списку и выберите анаграммы, используя наивный алгоритм, теперь список кандидатов значительно уменьшен

Ну пытается было бы проще проверить, существует ли слово. Поэтому, если вы поместите весь словарь в trie:

http://en.wikipedia.org/wiki/Trie

затем вы можете потом взять свое слово и сделать простой отход, взяв символ и рекурсивно проверив, можем ли мы "ходить" по Трие с любой комбинацией остальных символов (добавляя по одному символу за раз). Когда все символы используются в ветви рекурсии, и в Три, тогда слово существует.

Trie помогает, потому что его хорошее состояние остановки: Мы можем проверить, является ли часть строки, e.g "Anag" является допустимым путем в trie, если нет, мы можем сломать эту ветвь пертикулярной рекурсии. Это означает, что нам не нужно проверять каждую перестановку символов.

в псевдо-код

checkAllChars(currentPositionInTrie, currentlyUsedChars, restOfWord)
    if (restOfWord == 0)
    {
         AddWord(currentlyUsedChar)
    }
    else 
    {
        foreach (char in restOfWord)
        {
            nextPositionInTrie = Trie.Walk(currentPositionInTrie, char)
            if (nextPositionInTrie != Positions.NOT_POSSIBLE)
            {
                checkAllChars(nextPositionInTrie, currentlyUsedChars.With(char), restOfWord.Without(char))
            }
        }   
    }

очевидно, вам нужна хорошая структура данных Trie, которая позволяет вам постепенно" ходить " по дереву и проверять на каждом узле, если существует путь с заданным char к любому следующему узлу...


static void Main(string[] args)
{

    string str1 = "Tom Marvolo Riddle";
    string str2 = "I am Lord Voldemort";

    str2=  str2.Replace(" ", string.Empty);
    str1 = str1.Replace(" ", string.Empty);
    if (str1.Length != str2.Length)
        Console.WriteLine("Strings are not anagram");
    else
    {
        str1 = str1.ToUpper();
        str2 = str2.ToUpper();
        int countStr1 = 0;
        int countStr2 = 0;
        for (int i = 0; i < str1.Length; i++)
        {
            countStr1 += str1[i];
            countStr2 += str2[i];

        }
        if(countStr2!=countStr1)
            Console.WriteLine("Strings are not anagram");
        else Console.WriteLine("Strings are  anagram");

    }
    Console.Read();
}

  • уменьшить слова, чтобы-сказать-нижний регистр (clojure.string/lower-case).
  • классифицировать их (group-by) по частоте письма-карта (frequencies).
  • падение частоты карты,
  • ... оставляя коллекции анаграмм.

(These) являются соответствующие функции в Lisp диалекте Clojure.

вся функция может быть выражена так:

(defn anagrams [dict]
  (->> dict
       (map clojure.string/lower-case)
       (group-by frequencies)
       vals))

например,

(anagrams ["Salt" "last" "one" "eon" "plod"])
;(["salt" "last"] ["one" "eon"] ["plod"])

индексирования функция, которая сопоставляет каждую вещь с ее коллекцией, -

(defn index [xss]
  (into {} (for [xs xss, x xs] [x xs])))

так, что, например,

((comp index anagrams) ["Salt" "last" "one" "eon" "plod"])
;{"salt" ["salt" "last"], "last" ["salt" "last"], "one" ["one" "eon"], "eon" ["one" "eon"], "plod" ["plod"]}

... где comp - это функциональное состава.


создание всех перестановок легко, я думаю, вы беспокоитесь, что проверка их существования в словаре является "очень неэффективной" частью. Но это зависит от того, какую структуру данных вы используете для словаря: конечно, список слов будет неэффективен для вашего варианта использования. Кстати о нах, они, вероятно, были бы идеальным представлением и довольно эффективными.

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


Это зависит от того, как вы храните свой словарь. Если это простой массив слов, ни один алгоритм не будет быстрее линейного.

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

  1. обозначьте свой словарь как D, текущий префикс как S. S = 0;
  2. вы создаете карту частот для своего слова. Обозначим его Ф.
  3. используя двоичный поиск, найдите указатели для начала каждая буква в словаре. Обозначим этот массив указателей П.
  4. для каждого символа c от A до Z, если F[c] == 0, пропустите его, иначе
    • S += c;
    • F[c] --;
    • P
    • рекурсивно вызовите Шаг 4, пока не найдете совпадение для своего слова или пока не обнаружите, что такого совпадения не существует.

во всяком случае, так я бы это сделал. Не должно будьте более традиционным подходом, но это быстрее, чем линейный.


попытался реализовать решение hashmap

public class Dictionary {

    public static void main(String[] args){

    String[] Dictionary=new String[]{"dog","god","tool","loot","rose","sore"};

    HashMap<String,String> h=new HashMap<String, String>();

    QuickSort q=new QuickSort();

    for(int i=0;i<Dictionary.length;i++){

        String temp =new String();

        temp= q.quickSort(Dictionary[i]);//sorted word e.g dgo for dog

        if(!h.containsKey(temp)){
           h.put(temp,Dictionary[i]);
        }

        else
        {
           String s=h.get(temp);
           h.put(temp,s + " , "+ Dictionary[i]);
        }
    }

    String word=new String(){"tolo"};

    String sortedword = q.quickSort(word);

    if(h.containsKey(sortedword.toLowerCase())){ //used lowercase to make the words case sensitive

        System.out.println("anagrams from Dictionary   :  " + h.get(sortedword.toLowerCase()));
    }

}

  • вычислить вектор количества частот для каждого слова в словаре, вектор длины списка алфавита.
  • генерировать случайный гауссов вектор длины списка алфавита
  • проецируйте вектор количества слов каждого словаря в этом случайном направлении и сохраните значение (вставьте так, чтобы массив значений был отсортирован).

  • учитывая новое тестовое слово, проецируйте его в том же случайном направлении, что и словарь слова.

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

PS: вышеуказанная процедура является обобщением процедуры простого числа, которая потенциально может привести к большим числам (и, следовательно, к вычислительной точности)


одним из решений является - Сопоставьте простые числа с символами алфавита и умножьте простое число

For ex - 

    a -> 2
    b -> 3
    ......
    .......
    ......
    z -> 101

Так

'ab' -> 6
'ba' -> 6
'bab' -> 18
'abba' -> 36
'baba' -> 36

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


сначала проверьте, одинакова ли длина строк. затем проверьте, одинакова ли сумма символов в обеих строках (т. е. сумма кода ascii) тогда слова анаграммы иначе не анаграмма