2つの単語の1つが別の単語とまったく同じ文字である場合、2つの単語はアナグラムです。
例:Anagram
&Nagaram
はアナグラム(大文字と小文字を区別しない)です。
今、これに似た多くの質問があります。 2つの文字列がアナグラムであるかどうかを確認する方法は次のとおりです。
1)Sort
文字列を比較します。
2)これらの文字列に対してfrequency map
を作成し、それらが同じかどうかを確認します。
ただし、この場合、Wordが与えられます(簡単にするために、単一のWordのみを想定し、単一のWordアナグラムのみを使用します)。そのためのアナグラムを見つける必要があります。
私が念頭に置いている解決策は、すべての順列を生成する Wordで、これらの単語のどれを確認するか辞書に存在するです。しかし、明らかに、これは非常に非効率的です。はい、辞書も利用可能です。
それで、ここにはどのような選択肢がありますか?
私は同様のスレッドでTries
を使用して何かを行うことができることを読みましたが、人はアルゴリズムが何であるか、なぜ最初にTrieを使用したのかについて説明しませんでした、実装も提供されましたin PythonまたはRuby。だからそれは本当に役に立たなかったので、この新しいスレッドを作成しました。誰かが実装を共有したい場合(C、C++またはJava以外)それも。
アルゴリズムの例:
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
構築が比較的高速で、検索が非常に高速です。
新しいソリューションを思いついた。算術の基本定理を使用します。そのため、最初の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;
}
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
の単語のリストです。
距離ごとにマップを作成します。
次に、ルックアップは次のアルゴリズムになります。
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
は機能合成演算子です。
よく試してみると、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))
}
}
}
明らかに、次のノードへの指定された文字を持つパスがあるかどうかツリーを徐々に「歩いて」各ノードでチェックできるニーストライデータ構造が必要です...
ハッシュマップソリューションを実装しようとしました
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:上記の手順は素数の手順を一般化したものであり、潜在的に大きな数につながる可能性があります(したがって、計算精度の問題)。
すべての順列を生成するのは簡単です。辞書でそれらの存在を確認することは「非常に非効率的な」部分であると心配していると思います。しかし実際には、辞書に使用するデータ構造に依存します。もちろん、単語のリストはユースケースにとって非効率的です。 Tries といえば、おそらく理想的な表現であり、非常に効率的です。
別の可能性は、辞書に対して前処理を行うことです。キーがソートされたWordの文字であり、値がワードのリストであるハッシュテーブルを構築します。このハッシュテーブルをシリアル化して、ファイルに書き込み、後ですぐにリロードすることもできます。次に、アナグラムを検索するには、指定されたWordを並べ替えて、ハッシュテーブル内の対応するエントリを検索します。