web-dev-qa-db-ja.com

特定の単語のアナグラムを見つける

2つの単語の1つが別の単語とまったく同じ文字である場合、2つの単語はアナグラムです。

例:AnagramNagaramはアナグラム(大文字と小文字を区別しない)です。

今、これに似た多くの質問があります。 2つの文字列がアナグラムであるかどうかを確認する方法は次のとおりです。

1)Sort文字列を比較します。

2)これらの文字列に対してfrequency mapを作成し、それらが同じかどうかを確認します。

ただし、この場合、Wordが与えられます(簡単にするために、単一のWordのみを想定し、単一のWordアナグラムのみを使用します)。そのためのアナグラムを見つける必要があります。

私が念頭に置いている解決策は、すべての順列を生成する Wordで、これらの単語のどれを確認するか辞書に存在するです。しかし、明らかに、これは非常に非効率的です。はい、辞書も利用可能です。

それで、ここにはどのような選択肢がありますか?

私は同様のスレッドでTriesを使用して何かを行うことができることを読みましたが、人はアルゴリズムが何であるか、なぜ最初にTrieを使用したのかについて説明しませんでした、実装も提供されましたin PythonまたはRuby。だからそれは本当に役に立たなかったので、この新しいスレッドを作成しました。誰かが実装を共有したい場合(C、C++またはJava以外)それも。

38
h4ck3d

アルゴリズムの例:

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

特定のWordのすべてのアナグラムを確認するには:

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

構築が比較的高速で、検索が非常に高速です。

72
Vatine

新しいソリューションを思いついた。算術の基本定理を使用します。そのため、最初の26個の素数の配列を使用するという考え方です。次に、入力Wordの各文字について、対応する素数A = 2、B = 3、C = 5、D = 7…を取得し、入力Wordの積を計算します。次に、辞書の各Wordに対してこれを行い、Wordが入力Wordと一致する場合、結果リストに追加します。すべてのアナグラムには同じ署名があります。

1より大きい整数は素数であるか、または素数の一意の積として記述できます(順序は無視されます)。

これがコードです。 Wordを大文字に変換し、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;
}
17
ACV

2つの単語の長さが同じでない場合、それらはアナグラムではないことがわかります。したがって、同じ長さの単語のグループに辞書を分割できます。

現在、これらのグループの1つだけに焦点を当てており、基本的にすべての単語はこの小さな宇宙ではまったく同じ長さを持っています。

各文字の位置が次元であり、その次元の値が文字に基づいている場合(たとえば、ASCIIコード)。その後、Wordベクトルの長さを計算できます。

たとえば、「A」= 65、「B」= 66、その後length("AB") = sqrt(65*65 + 66*66)と言います。明らかに、length("AB") = length("BA")

明らかに、2つのWordがアナグラムの場合、それらのベクトルの長さは同じです。次の質問は、2つのWord(同じ文字数の)ベクトルが同じ長さである場合、それらはアナグラムですか?直観的には、その長さを持つすべてのベクトルが球を形成するため、いいえと言います。このケースでは整数空間にいるので、実際にはいくつあるのかわかりません。

ただし、少なくとも辞書をさらに分割することができます。辞書の各単語について、ベクトルの距離を計算します:for(each letter c) { distance += c*c }; distance = sqrt(distance);

次に、長さnのすべての単語のマップを作成し、距離でキーを設定します。値は、その特定の距離をもたらす長さnの単語のリストです。

距離ごとにマップを作成します。

次に、ルックアップは次のアルゴリズムになります。

  1. Wordの長さに基づいて正しい辞書マップを使用する
  2. Wordのベクトルの長さを計算する
  3. その長さに一致する単語のリストを検索します
  4. リストを調べて、素朴なアルゴリズムを使用してアナグラムを選択すると、候補リストが大幅に削減されます
2
mprivat
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();
}
1
KrtkNyk
  • 単語を-言う-小文字(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
Thumbnail

よく試してみると、Wordが存在するかどうかを簡単に確認できます。したがって、辞書全体をトライに入れると:

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

その後、Wordを取得し、charを取得し、残りのcharsを組み合わせてTrieを「歩く」ことができるかどうかを再帰的に確認することにより、単純なバックトラッキングを実行できます(一度に1つのcharを追加します)。すべての文字が再帰分岐で使用され、Trieに有効なパスがあった場合、Wordは存在します。

トライは、ニースの停止条件のために役立ちます。文字列の一部、たとえば「Anag」がトライ内の有効なパスであるかどうかを確認できます。そうでない場合は、特定の再帰分岐を解除できます。これは、文字のすべての順列をチェックする必要がないことを意味します。

擬似コードで

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))
            }
        }   
    }

明らかに、次のノードへの指定された文字を持つパスがあるかどうかツリーを徐々に「歩いて」各ノードでチェックできるニーストライデータ構造が必要です...

1
Daniel

ハッシュマップソリューションを実装しようとしました

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()));
    }

}
0
megha

それは辞書の保存方法に依存します。単純な単語の配列である場合、線形より高速なアルゴリズムはありません。

ソートされている場合、ここでうまくいくアプローチがあります。私は今それを発明しましたが、線形アプローチよりも速いと思います。

  1. 辞書をD、現在の接頭辞をSとして示します。S= 0;
  2. Wordの頻度マップを作成します。 Fで示しましょう。
  3. バイナリ検索を使用して、辞書内の各文字の先頭へのポインターを検索します。このポインターの配列をPで示しましょう。
  4. AからZまでの各char cについて、F [c] == 0の場合はスキップし、そうでない場合は
    • S + = c;
    • F [c]-;
    • P <-すべての文字iに対してP [i] = S + iで始まる最初のワードへのポインター。
    • Wordに一致するものが見つかるまで、またはそのような一致がないことがわかるまで、手順4を再帰的に呼び出します。

とにかく、これは私がそれをする方法です。より一般的なアプローチが必要ですが、これは線形よりも高速です。

0
Saage
  • 辞書内の各単語の頻度カウントベクトル、アルファベットリストの長さのベクトルを計算します。
  • アルファベットリストの長さのランダムなガウスベクトルを生成する
  • 各辞書の単語のカウントベクトルをこのランダムな方向に射影し、値を保存します(値の配列がソートされるように挿入します)。

  • 新しいテストワードが与えられたら、辞書のワードに使用されるのと同じランダムな方向にそれを投影します。

  • バイナリ検索を実行して、同じ値にマップされる単語のリストを見つけます。
  • 上記で取得した各Wordが実際にアナグラムであるかどうかを確認します。そうでない場合は、リストから削除します。
  • リストの残りの要素を返します。

PS:上記の手順は素数の手順を一般化したものであり、潜在的に大きな数につながる可能性があります(したがって、計算精度の問題)。

0
Vedarun

すべての順列を生成するのは簡単です。辞書でそれらの存在を確認することは「非常に非効率的な」部分であると心配していると思います。しかし実際には、辞書に使用するデータ構造に依存します。もちろん、単語のリストはユースケースにとって非効率的です。 Tries といえば、おそらく理想的な表現であり、非常に効率的です。

別の可能性は、辞書に対して前処理を行うことです。キーがソートされたWordの文字であり、値がワードのリストであるハッシュテーブルを構築します。このハッシュテーブルをシリアル化して、ファイルに書き込み、後ですぐにリロードすることもできます。次に、アナグラムを検索するには、指定されたWordを並べ替えて、ハッシュテーブル内の対応するエントリを検索します。

0
Artyom