あいたい名怜玢

こんにちは 怜玢、サヌビス、たたは補品に関する問題は、ほずんどのサむトで発生したす。 そしお、ほずんどの堎合、そのような機䌚の実装は、怜玢行に入力された正確な単語による怜玢に限定されたす。

時間があり、顧客がもう少し欲しい堎合は、最も人気のあるアルゎリズム「レヌベンシュタむン距離」の実装をグヌグルで怜玢しお入力したす。

この蚘事では、高床に修正されたアルゎリズムに぀いお説明したすが、レヌベンシュタむン距離に基づいお、名前によるファゞヌ怜玢のCコヌドの䟋を瀺したす。たずえば、カフェ、レストラン、特定のサヌビスなどです。構成のいく぀かの単語

Yandex、Mail、ProjectArmata、戊車の䞖界、軍艊の䞖界、軍甚機の䞖界など

アルゎリズムに慣れおいない人のために、最初に「 Implementing Fuzzy Search 」、「 Fuzzy Search in Text and Dictionary 」、たたはスラむダヌの䞋のプレれンテヌションの説明を読むこずをお勧めしたす。

レヌベンシュタむン距離アルゎリズム
レヌベンシュタむン距離は、2぀の単語の違いの皋床を芋぀けるためのネットワヌクで最も䞀般的なアルゎリズムです。 ぀たり、1行目から2行目を取埗するために実行する必芁があるアクションの最小数は䜕ですか。

そのようなアクションは3぀しかありたせん。

•取り倖し
•挿入
•亀換

たずえば、2぀の行「CONNECT」ず「CONEHEAD」の堎合、次の倉換テヌブルを䜜成できたす。



理論的には、取匕䟡栌は操䜜の皮類挿入、削陀、亀換および/たたは関連するシンボルに䟝存する堎合がありたす。 しかし、䞀般的な堎合

wa、ε-シンボル「a」を削陀する䟡栌は1
wε、b-蚘号「b」を挿入する䟡栌は1
wa、b-文字「a」を文字「b」で眮き換える䟡栌は1

どこでもナニットを眮きたす。

そしお、数孊の芳点からは、蚈算は次のようになりたす。

S1ずS2をアルファベット䞊の2本の線それぞれ長さMずNずし、線集距離レヌベンシュタむン距離dS1、S2は次の繰り返し匏を䜿甚しお蚈算できたす。



レヌベンシュタむンアルゎリズムの「デフォルトの実装」は、2人の数孊者WangerずFisherによっお行われたした[チェスプレヌダヌず混同しないでください]。

そしお、Cでは次のようになりたす。

private Int32 levenshtein(String a, String b) { if (string.IsNullOrEmpty(a)) { if (!string.IsNullOrEmpty(b)) { return b.Length; } return 0; } if (string.IsNullOrEmpty(b)) { if (!string.IsNullOrEmpty(a)) { return a.Length; } return 0; } Int32 cost; Int32[,] d = new int[a.Length + 1, b.Length + 1]; Int32 min1; Int32 min2; Int32 min3; for (Int32 i = 0; i <= d.GetUpperBound(0); i += 1) { d[i, 0] = i; } for (Int32 i = 0; i <= d.GetUpperBound(1); i += 1) { d[0, i] = i; } for (Int32 i = 1; i <= d.GetUpperBound(0); i += 1) { for (Int32 j = 1; j <= d.GetUpperBound(1); j += 1) { cost = Convert.ToInt32(!(a[i-1] == b[j - 1])); min1 = d[i - 1, j] + 1; min2 = d[i, j - 1] + 1; min3 = d[i - 1, j - 1] + cost; d[i, j] = Math.Min(Math.Min(min1, min2), min3); } } return d[d.GetUpperBound(0), d.GetUpperBound(1)]; } 

ここから撮圱 。

そしお、芖芚的に「Arestant」および「Dagestan」ずいう単語に察するアルゎリズムの動䜜は次のように衚されたす。



マトリックスの右䞋隅は、単語の違いを瀺しおいたす。 マトリックスから、この特定のケヌスでは、単語間の違いは3぀の条件付きオりムです。

明確でない堎合は、これらの単語を別のビュヌで芋おください。

_ A R E S T A N T
D A G E S T A N _

したがっお、単語「Arestant」ず「Dagestan」を有効にするには、1぀の文字「D」を远加し、1぀の文字「P」を「G」に眮き換え、文字「T」を削陀する必芁がありたす。 なぜなら すべおのアクションの重みは1で、蚀葉の違いは3オりムです。

単語が完党に䞀臎する堎合、距離は0になりたす。これが理論党䜓であり、独創的なものはすべお単玔です。

そしお、それは芋えるだろう-ここにある すべおが私たちのために発明され、䜕もするこずはありたせんが、問題がありたす...

1タむプミスの可胜性は、キヌボヌド䞊のキヌの距離、音声グルヌプ、および通垞のタむピング速床に䟝存するため、重み係数を1にするこずは垞に完党に正しいずは限りたせん。
2レヌベンシュタむンのアむデアは、単語の䞀郚ではなく「単語の違いを芋぀ける」こずを目的ずしおいたす。これは、文字を入力するずきに動的に生成される結果にずっお重芁です。
3サヌビスの名前には、構成に耇数の単語が含たれおいるため、人は単玔に順序を芚えおいない堎合がありたす。

たた、次のようないく぀かの芁玠も考慮したす。

•別の蚀語のキヌボヌドレむアりト
•文字の音蚳

これらは、この蚘事で解決しようずする問題です。

たず、すべおの単語が1぀のレゞスタに぀ながるこずに同意したしょう。 私のバヌゞョンのコヌドでは、小文字を遞択したした。これは、必芁な参照に反映されたすこれらの参照は、説明の過皋で䞎えられたす。 蚘事自䜓では、CamelCase-ProjectArmataなど、さたざたな曞䜓に頌りたすが、これは人間の知芚の䟿宜のためだけに行われたす。分析の芳点からするず、レゞスタは1぀䞋です。 そしおただ、私たちはベヌスずしお叀兞ではなく、 ここからレヌベンシュタむン距離を芋぀けるためのコヌドの最適化されたバヌゞョンを取りたす 

語順の倉曎を削陀するこずにより、これをわずかに修正したす。 レヌベンシュタむンのアルゎリズムでは、語順は重芁ではありたせんが、私たちにずっおは他の理由で重芁です。 その結果、次のものを受け取りたした。

 public int LevenshteinDistance(string source, string target){ if(String.IsNullOrEmpty(source)){ if(String.IsNullOrEmpty(target)) return 0; return target.Length; } if(String.IsNullOrEmpty(target)) return source.Length; var m = target.Length; var n = source.Length; var distance = new int[2, m + 1]; // Initialize the distance 'matrix' for(var j = 1; j <= m; j++) distance[0, j] = j; var currentRow = 0; for(var i = 1; i <= n; ++i){ currentRow = i & 1; distance[currentRow, 0] = i; var previousRow = currentRow ^ 1; for(var j = 1; j <= m; j++){ var cost = (target[j - 1] == source[i - 1] ? 0 : 1); distance[currentRow, j] = Math.Min(Math.Min( distance[previousRow, j] + 1, distance[currentRow, j - 1] + 1), distance[previousRow, j - 1] + cost); } } return distance[currentRow, m]; } 

重み係数を倉曎するこずにより、怜玢アルゎリズムを改善し始めたす。 たず、挿入および削陀の芁玠は2です。

぀たり 行が倉曎されたす

 for(var j = 1; j <= m; j++) distance[0, j] = j * 2; ... distance[currentRow, 0] = i * 2; ... distance[previousRow, j] + 2 ... distance[currentRow, j - 1] + 2 

そしお、文字の眮換係数を蚈算する行がありたす。関数CostDistanceSymbolを有効にしたす

 var cost = (target[j - 1] == source[i - 1] ? 0 : 1); 

そしお、2぀の芁因を怜蚎したす。

1キヌボヌドの距離
2音声グルヌプ

この点で、゜ヌスオブゞェクトずタヌゲットオブゞェクトのより䟿利な䜜業のために、それらをオブゞェクトに倉換したす。

 class Word { //   public string Text { get; set; } //   public List<int> Codes { get; set; } = new List<int>(); } 

これには、次の補助ガむドが必芁です。

ロシア語キヌボヌドのキヌコヌドの比率

 private static SortedDictionary<char, int> CodeKeysRus = new SortedDictionary<char, int> { { '' , 192 }, { '1' , 49 }, { '2' , 50 }, ... { '-' , 189 }, { '=' , 187 }, { '' , 81 }, { '' , 87 }, { '' , 69 }, ... { '_' , 189 }, { '+' , 187 }, { ',' , 191 }, } 

英語キヌボヌドのキヌコヌド比

 private static SortedDictionary<char, int> CodeKeysEng = new SortedDictionary<char, int> { { '`', 192 }, { '1', 49 }, { '2', 50 }, ... { '-', 189 }, { '=', 187 }, { 'q', 81 }, { 'w', 87 }, { 'e', 69 }, { 'r', 82 }, ... { '<', 188 }, { '>', 190 }, { '?', 191 }, }; 

数孊蚀語で話すこれらの2぀のディレクトリのおかげで、2぀の異なるシンボルスペヌスを1぀のナニバヌサルに倉換できたす。

たた、次の関係が有効です。

private static SortedDictionary <int、List> DistanceCodeKey = new SortedDictionary <int、

 List<int>> { /* '`' */ { 192, new List<int>(){ 49 }}, /* '1' */ { 49 , new List<int>(){ 50, 87, 81 }}, /* '2' */ { 50 , new List<int>(){ 49, 81, 87, 69, 51 }}, ... /* '-' */ { 189, new List<int>(){ 48, 80, 219, 221, 187 }}, /* '+' */ { 187, new List<int>(){ 189, 219, 221 }}, /* 'q' */ { 81 , new List<int>(){ 49, 50, 87, 83, 65 }}, /* 'w' */ { 87 , new List<int>(){ 49, 81, 65, 83, 68, 69, 51, 50 }}, ... /* '>' */ { 188, new List<int>(){ 77, 74, 75, 76, 190 }}, /* '<' */ { 190, new List<int>(){ 188, 75, 76, 186, 191 }}, /* '?' */ { 191, new List<int>(){ 190, 76, 186, 222 }}, }; 

぀たり 別のキヌの呚りに立っおいるキヌを取りたす。 図の䟋によっおこれを確認できたす



はい、QWERTYキヌボヌドに加えお、他のレむアりトがあり、キヌボヌド距離テヌブルが䞀臎しないこずを知っおいたすが、最も䞀般的なオプションを䜿甚したす。

誰かがより良い方法を知っおいるなら、曞いおください。

これで、最初のステップ-CostDistanceSymbol関数で゚ラヌのあるシンボルの重みを蚈算する準備ができたした。

前ず同様に、文字が同じ堎合、距離は0です。

 if (source.Text[sourcePosition] == target.Text[targetPosition]) return 0; 

キヌコヌドが同じ堎合、距離も0です。

 if (source.Codes[sourcePosition] != 0 && target.Codes[targetPosition] == target.Codes[targetPosition]) return 0; 

文字を比范した埌にキヌコヌドを比范する理由がわからない堎合、答えは簡単です。「Water」ず「Djlf」ずいう蚀葉を同じように理解しおもらいたいのです。 たた、レむアりトに関係なく、異なるレむアりトで入力できる文字、たずえば「;」や「、」も同じように認識されおいたした。

さらに、キヌコヌドが互いにどれだけ近いかに぀いおは、すでにコヌドだけを芋おいきたす。 近い堎合、距離は1、そうでない堎合、2挿入たたは削陀時ずたったく同じ重量

 int resultWeight = 0; List<int> nearKeys; if (!DistanceCodeKey.TryGetValue(source.Codes[sourcePosition], out nearKeys)) resultWeight = 2; else resultWeight = nearKeys.Contains(target.Codes[searchPosition]) ? 1 : 2; 

実際、このような小さな改良は、間違ったレむアりトから始たり、キヌによるミスで終わる膚倧な数のランダム゚ラヌをカバヌしたす。

しかし、タむプミスの埌では、人は単語の぀づり方を知らないかもしれたせん。 䟋パむク、䞋品。 テスト。

そのような蚀葉はロシア語だけでなく、英語でもありたす。 そのような堎合も考慮する必芁がありたす。 英語の単語に぀いおは、音声グルヌプに関するZobelずDarthの研究を基本ずしたす 。

「Aeiouy」、「bp」、「ckq」、「dt」、「lr」、「mn」、「gj」、「fpv」、「sxz」、「csz」

そしお、ロシア人のために、私は自分自身で䜜曲したす

「Yy」、「eee」、「ay」、「oye」、「uy」、「shch」、「oa」、「yo」

これらの音声グルヌプをタむプのオブゞェクトに倉換したす。

 PhoneticGroupsEng = { { 'a', { 'e', 'i', 'o', 'u', 'y'} }, { 'e', { 'a', 'i', 'o', 'u', 'y'} } ... } 

これは手䜜業でもコヌドの蚘述方法でも行えたすが、結果は同じです。 そしお今、キヌコヌドをチェックした埌、前のステップず同じ゚ラヌを芋぀けるためのロゞックで音声グルヌプに入るための文字をチェックするこずができたす

 List<char> phoneticGroups; if (PhoneticGroupsRus.TryGetValue(target.Text[targetPosition], out phoneticGroups)) resultWeight = Math.Min(resultWeight, phoneticGroups.Contains(source.Text[sourcePosition]) ? 1 : 2); if (PhoneticGroupsEng.TryGetValue(target.Text[targetPosition], out phoneticGroups)) resultWeight = Math.Min(resultWeight, phoneticGroups.Contains(source.Text[sourcePosition]) ? 1 : 2); 

䞊蚘のタむプミスに加えお、テキストの「タむピング速床」のタむプミスがありたす。 これは、2぀の連続した文字が入力されたずきに混同される堎合です。 さらに、これはある皮の数孊者フレデリック・ダメラりが文字の転眮順列の操䜜を远加するこずでレヌベンシュタむンアルゎリズムを完成させたずいうよくある間違いです。

゜フトりェアの芳点から、LevenshteinDistance関数に次を远加したす。


 if (i > 1 && j > 1 && source.Text[i - 1] == target.Text[j - 2] && source.Text[i - 2] == target.Text[j - 1]) { distance[currentRow, j] = Math.Min(distance[currentRow, j], distance[(i - 2) % 3, j - 2] + 2); } 

発蚀
基瀎ずしお採甚した最適化コヌドには、距離行列を初期化する次の圢匏がありたす。var distance = new int [2、m + 1];

したがっお、「distance [i-23、...」ずいうコヌドのこのセクションは珟圚の圢匏では機胜したせん。蚘事の最埌に正しいバヌゞョンの関数を瀺したす。

したがっお、コヌドを完成させる最初のステップを完了したした。 2番目のポむントに進みたす。 レヌベンシュタむンのアむデアは、単語の䞀郚ではなく、「単語を区別する」こずを目的ずしおいるこずを思い出しおください。これは、文字を入力する際の動的な出力にずっお重芁です。

たずえば、ディレクトリには2぀の単語がありたす。

•「ProjectArmata」
•「ダク」

ク゚リ「Pro」を怜玢バヌに入力するず、2぀の文字を眮き換えお1぀を削陀するだけで3぀のオりムになるため、「ダク」が優先床の高いものになりたすナニット係数ず埓来のレヌベンシュタむンアルゎリズムを考慮せずに倉曎したす。 「jectArmata」ずいう単語の䞀郚を10個のオりムに远加したす。

この状況で最も論理的なのは、単語党䜓ではなく、怜玢された単語の䞀郚のみを入力された文字列ず比范するこずです。

なぜなら 怜玢ク゚リは「Pro」ずいう3文字で構成されおいたす。比范察象の「ProjectArmata」ずいう単語から最初の3文字を取埗したす。 「プロ」ず100の䞀臎を取埗したす。 この特定の堎合-完璧。 しかし、さらにいく぀かのオプションを芋おみたしょう。 デヌタベヌスに次の単語セットがあるずしたす。

•「共同」
•「コンベダヌ」
•「コロニヌ」
•「化粧品」

怜玢ク゚リは「Com」のようになりたす。 その結果、単語の䞀臎率は次のようになりたす。

共同-0
コンベア-1
コロニヌ-1
化粧品-1

「Kommunalka」ずいう単語ですべおが順調であれば、他の3぀の単語は䜕らかの圢で統䞀されおいるように芋えたす。 そしお私たちの仕事は、すべおを䞀列に䞊べるのではなく、圌に最適な結果を䞎えるこずです。 さらに、ダメラりが蚀ったように、ほずんどの間違いは文字の順列です。

このような間違いをなくすために、小さな修正を行いたす。

最初の「n」文字ではなく、「n + 1」文字を取りたす。nはク゚リ内の文字数です。 そしお、「Com」のリク゚ストでの係数は次のようになりたす。

共同-1
化粧品-1
コンベダヌ-2
コロニヌ-2

「化粧品」は修正されたしたが、「コムムナルカ」は残りたした...しかし、私は次の理由でこのオプションがより奜きです。 通垞、怜玢が必芁な堎合、最初に文字を入力するず、怜玢バヌの䞋にドロップダりンリストの圢匏でナヌザヌに情報が衚瀺されたす。 このドロップダりンリストの長さは、3〜7゚ントリのサむズによっお制限されたす。 その結果、3぀の゚ントリしかない堎合、2番目のバヌゞョンでは「Kommunalka」、「Cosmetics」、「Conveyor」が衚瀺されたす[as それは、guidのために、たたは単に䜜成日のために、最初の発行では愚かです。 そしお、最初のケヌスでは、「Kommunalka」、「Conveyor」、「Colony」があり、「Cosmetics」はありたせん。 圌女は他の理由で䞍運でした...

もちろん、この問題には他の解決策もありたす。 たずえば、最初に「n」文字で゜ヌトしおから、むンデックスが䞀臎する単語のグルヌプを取埗し、さらに「n + 1」文字を再゜ヌトするず、䜕床も䜕床も再゜ヌトできたす...解決される問題、蚈算胜力。

今、䞊蚘の問題の解決に焊点を圓おないでください...私たちは道の真っin䞭にいるだけで、ただ䌝えたいこずがありたす。

正しい怜玢結果の次のニュアンスは、゜連の時代からもたらされたす。 その埌、いく぀かの単語を1぀にたずめた名前を䜜るのが奜きでした。 はい、今ではそれが関連しおいたす

消費者組合
GazPromBank
ロシア蟲業銀行
Projectarmata
銀行URALSIB
等

[ps 名前の䞀郚がスペヌスで曞かれおいるこずは知っおいたすが、鮮明な䟋を取り䞊げる必芁がありたした]

しかし、アルゎリズムに埓えば、垞に単語の最初の「n + 1」文字を取埗したす。 たた、「Bank」ずいう単語を入力するず、適切な匕き枡しは行われたせん。 なぜなら 行を比范したす。

BankU
ポトレ
ガスプル
ロッセ
「プロゞェクト」

䜍眮に関係なく「bank」ずいう単語を芋぀けるには、フレヌズごずにフロヌティングりィンドりを䜜成し、最䜎の係数を返す必芁がありたす。

 double GetRangeWord(Word source, Word target) { double rangeWord = double.MaxValue; Word croppedSource = new Word(); int length = Math.Min(source.Text.Length, target.Text.Length + 1); for (int i = 0; i <= source.Text.Length - length; i++) { croppedSource.Text = target.Text.Substring(i, length); croppedSource.Codes = target.Codes.Skip(i).Take(length).ToList(); rangeWord = Math.Min(LevenshteinDistance(croppedSource, target) + (i * 2 / 10.0), rangeWord); } return rangeWord; } 

ご芧のずおり、レヌベンシュタむン距離を蚈算した埌に埗られた誀差に、別の倀i * 2 / 10.0を远加したす。 「i * 2」の堎合-すべおが明確な堎合、これは、レヌベンシュタむン距離を芋぀けるための叀兞的なアルゎリズムのように、単語の先頭に文字を挿入する゚ラヌですが、なぜ10で割るのですか 芁するに、「i * 2」だけを残すず、短い単語の長さが短くなり、再び銀行の名前を残すこずになりたす。 したがっお、係数を10で陀算する必芁があり、これによりこのバむアスが枛少したす。 なぜちょうど10 私たちのベヌスに぀いおは、通垞は倚少適合したすが、より倚くに分割できるこずを陀倖したせん。 すべおは、単語の長さず単語の類䌌性に䟝存したす。 最適な係数の蚈算に぀いおは少し埌で説明したす。

怜玢ナニットのように、䞊べ替えられた単語で、フレヌズに移動したす。 そしお、たず最初に、いく぀かの䟋を瀺したす。

•りォヌフェむス
•戊車の䞖界
•軍甚機の䞖界
•船の䞖界

フレヌズの怜玢から基本的に必芁なものを理解したしょう。 欠けおいる単語を远加したり、䞍芁なフレヌズを削陀したり、堎所を倉えたりする必芁がありたす。 すでにどこかでこれを蚀っおいたした...そしお、確かに、私は思い出したした...蚘事の冒頭で、レヌベンシュタむンの蚀葉ず距離関数に぀いお話したした。 あなたの時間を無駄にしないために、私はすぐにそれを玔粋な圢で䜿甚するこずはできなかったず蚀いたすが、それに觊発されお、フレヌズに適甚できるコヌドを曞くこずができたした。

レヌベンシュタむン距離関数の実装ず同様に、フレヌズの1぀が空の堎合、すべおの文字の挿入たたは削陀[空のフレヌズがどちら偎から来たかによる]に等しい゚ラヌ倀を返したす。

 if (!source.Words.Any()) { if (!search.Words.Any()) return 0; return search.Words.Sum(w => w.Text.Length) * 2 * 100; } if (!search.Words.Any()) { return source.Words.Sum(w => w.Text.Length) * 2 * 100; } 

぀たり フレヌズ内の文字数を芁玄し、2を掛けたすこの係数は文字の蚘事の冒頭で遞択したした。100を掛けたす。これらの100は、アルゎリズム党䜓で最も矛盟する係数です。 なぜ必芁なのかを以䞋でより明確に瀺し、その埌、理論的には倩井からだけでなく、蚈算する必芁があるこずを説明したす。

 double result = 0; for (int i = 0; i < search.Words.Count; i++) { double minRangeWord = double.MaxValue; int minIndex = 0; for (int j = 0; j < source.Words.Count; j++) { double currentRangeWord = GetRangeWord(source.Words[j], search.Words[i], translation); if (currentRangeWord < minRangeWord) { minRangeWord = currentRangeWord; minIndex = j; } } result += minRangeWord * 100 + (Math.Abs(i - minIndex) / 10.0); } return result; } 

このコヌドでは、デヌタベヌス内のレコヌドの各単語を各怜玢ク゚リず比范し、各単語の最䜎係数を取埗したす。 フレヌズの合蚈係数は次のずおりです。

result + = minRangeWord * 100 +Math.Abs​​i-minIndex/ 10.0;

ご芧のように、ロゞック党䜓は䞊蚘ず同じです。単語minRangeWordの最小係数に100を掛けお、単語が最適な䜍眮にどれだけ近いかを瀺す係数を远加したすMath.Abs​​i-minIndex/ 10.0。

前のステップで怜玢語の最適な䜍眮を怜玢するずきに発生する可胜性のある远加の係数を補正するために、100の乗算が䜿甚されたす。 その結果、この係数は、怜玢行のフレヌズずデヌタベヌス内のすべおの単語の間の最倧倀ずしお蚈算できたす。 フレヌズではなく、蚀葉で。 ただし、このためには、倉曎を加えおレヌベンシュタむン距離を「空にする」必芁がありたすが、これは非垞にリ゜ヌスを浪費したす。

぀たり GetRangeWord関数を実行し、フレヌズ「i * 2」の最適な堎所からの偏差の最倧倀を取埗したす。 そしお、最高倀を取埗した埌、最も近い10倍の数倀10、100、1000、10000、100000などに移動したす。 したがっお、2぀の倀を取埗したす。

最初の倀は、GetRangeWord関数で混合語を分割する倀です。 次に、前のオフセットを補正するためにminRangeWordを乗算する倀。 したがっお、フレヌズの類䌌性の正確な指暙を取埗したす。 しかし、実際には、倧きな偏差を無芖し、平均を倧たかに芋積もるこずができたす...私は実際にこれを行いたした。

原則ずしお、すべお。 私が敎理した䞻な問題は、「文字の音蚳」のほんの少しの改良です。 䞊蚘の怜玢ず音蚳の怜玢の違いは、CostDistanceSymbol関数ではキヌ距離に応じお応答倀を調敎しないこずです。 この堎合の発行は正しくありたせん。

たた、怜玢文字列の3文字で結果が適切に返されるこずにも泚意しおください。文字数が少ない堎合は、文字列を正確に䞀臎させるより䞍噚甚な方法を䜿甚するこずをお勧めしたす。

次に、䞊蚘のすべおの最も完党なコヌドを提䟛したすが、最初に

1このコヌドは、私が自由な時間に個人的に曞いたものです。
2アルゎリズムは、私が自由時間に個人的に考えたものです。

それずは別に、リンクずむンスピレヌションに感謝したす。DmitryPanyushkin、Pavel Grigorenko。
蚘事に蚘茉されおいる名前は、オヌプン゜ヌスから取埗されたものであり、所有者のものです。 広告ではありたせん。

読んでくれたみんなに感謝したす。 批刀やアドバむスを歓迎したす。

完党なコヌド
 public class DistanceAlferov { class Word { public string Text { get; set; } public List<int> Codes { get; set; } = new List<int>(); } class AnalizeObject { public string Origianl { get; set; } public List<Word> Words { get; set; } = new List<Word>(); } class LanguageSet { public AnalizeObject Rus { get; set; } = new AnalizeObject(); public AnalizeObject Eng { get; set; } = new AnalizeObject(); } List<LanguageSet> Samples { get; set; } = new List<LanguageSet>(); public void SetData(List<Tuple<string, string>> datas) { List<KeyValuePair<char, int>> codeKeys = CodeKeysRus.Concat(CodeKeysEng).ToList(); foreach (var data in datas) { LanguageSet languageSet = new LanguageSet(); languageSet.Rus.Origianl = data.Item1; if (data.Item1.Length > 0) { languageSet.Rus.Words = data.Item1.Split(' ').Select(w => new Word() { Text = w.ToLower(), Codes = GetKeyCodes(codeKeys, w) }).ToList(); } languageSet.Eng.Origianl = data.Item2; if (data.Item2.Length > 0) { languageSet.Eng.Words = data.Item2.Split(' ').Select(w => new Word() { Text = w.ToLower(), Codes = GetKeyCodes(codeKeys, w) }).ToList(); } this.Samples.Add(languageSet); } } public List<Tuple<string, string, double, int>> Search(string targetStr) { List<KeyValuePair<char, int>> codeKeys = CodeKeysRus.Concat(CodeKeysEng).ToList(); AnalizeObject originalSearchObj = new AnalizeObject(); if (targetStr.Length > 0) { originalSearchObj.Words = targetStr.Split(' ').Select(w => new Word() { Text = w.ToLower(), Codes = GetKeyCodes(codeKeys, w) }).ToList(); } AnalizeObject translationSearchObj = new AnalizeObject(); if (targetStr.Length > 0) { translationSearchObj.Words = targetStr.Split(' ').Select(w => { string translateStr = Transliterate(w.ToLower(), Translit_Ru_En); return new Word() { Text = translateStr, Codes = GetKeyCodes(codeKeys, translateStr) }; }).ToList(); } var result = new List<Tuple<string, string, double, int>>(); foreach (LanguageSet sampl in Samples) { int languageType = 1; double cost = GetRangePhrase(sampl.Rus, originalSearchObj, false); double tempCost = GetRangePhrase(sampl.Eng, originalSearchObj, false); if (cost > tempCost) { cost = tempCost; languageType = 3; } //    tempCost = GetRangePhrase(sampl.Rus, translationSearchObj, true); if (cost > tempCost) { cost = tempCost; languageType = 2; } tempCost = GetRangePhrase(sampl.Eng, translationSearchObj, true); if (cost > tempCost) { cost = tempCost; languageType = 3; } result.Add(new Tuple<string, string, double, int>(sampl.Rus.Origianl, sampl.Eng.Origianl, cost, languageType)); } return result; } private double GetRangePhrase(AnalizeObject source, AnalizeObject search, bool translation) { if (!source.Words.Any()) { if (!search.Words.Any()) return 0; return search.Words.Sum(w => w.Text.Length) * 2 * 100; } if (!search.Words.Any()) { return source.Words.Sum(w => w.Text.Length) * 2 * 100; } double result = 0; for (int i = 0; i < search.Words.Count; i++) { double minRangeWord = double.MaxValue; int minIndex = 0; for (int j = 0; j < source.Words.Count; j++) { double currentRangeWord = GetRangeWord(source.Words[j], search.Words[i], translation); if (currentRangeWord < minRangeWord) { minRangeWord = currentRangeWord; minIndex = j; } } result += minRangeWord * 100 + (Math.Abs(i - minIndex) / 10.0); } return result; } private double GetRangeWord(Word source, Word target, bool translation) { double minDistance = double.MaxValue; Word croppedSource = new Word(); int length = Math.Min(source.Text.Length, target.Text.Length + 1); for (int i = 0; i <= source.Text.Length - length; i++) { croppedSource.Text = source.Text.Substring(i, length); croppedSource.Codes = source.Codes.Skip(i).Take(length).ToList(); minDistance = Math.Min(minDistance, LevenshteinDistance(croppedSource, target, croppedSource.Text.Length == source.Text.Length, translation) + (i * 2 / 10.0)); } return minDistance; } private int LevenshteinDistance(Word source, Word target, bool fullWord, bool translation) { if (String.IsNullOrEmpty(source.Text)) { if (String.IsNullOrEmpty(target.Text)) return 0; return target.Text.Length * 2; } if (String.IsNullOrEmpty(target.Text)) return source.Text.Length * 2; int n = source.Text.Length; int m = target.Text.Length; //TODO    ( ) int[,] distance = new int[3, m + 1]; // Initialize the distance 'matrix' for (var j = 1; j <= m; j++) distance[0, j] = j * 2; var currentRow = 0; for (var i = 1; i <= n; ++i) { currentRow = i % 3; var previousRow = (i - 1) % 3; distance[currentRow, 0] = i * 2; for (var j = 1; j <= m; j++) { distance[currentRow, j] = Math.Min(Math.Min( distance[previousRow, j] + ((!fullWord && i == n) ? 2 - 1 : 2), distance[currentRow, j - 1] + ((!fullWord && i == n) ? 2 - 1 : 2)), distance[previousRow, j - 1] + CostDistanceSymbol(source, i - 1, target, j - 1, translation)); if (i > 1 && j > 1 && source.Text[i - 1] == target.Text[j - 2] && source.Text[i - 2] == target.Text[j - 1]) { distance[currentRow, j] = Math.Min(distance[currentRow, j], distance[(i - 2) % 3, j - 2] + 2); } } } return distance[currentRow, m]; } private int CostDistanceSymbol(Word source, int sourcePosition, Word search, int searchPosition, bool translation) { if (source.Text[sourcePosition] == search.Text[searchPosition]) return 0; if (translation) return 2; if (source.Codes[sourcePosition] != 0 && source.Codes[sourcePosition] == search.Codes[searchPosition]) return 0; int resultWeight = 0; List<int> nearKeys; if (!DistanceCodeKey.TryGetValue(source.Codes[sourcePosition], out nearKeys)) resultWeight = 2; else resultWeight = nearKeys.Contains(search.Codes[searchPosition]) ? 1 : 2; List<char> phoneticGroups; if (PhoneticGroupsRus.TryGetValue(search.Text[searchPosition], out phoneticGroups)) resultWeight = Math.Min(resultWeight, phoneticGroups.Contains(source.Text[sourcePosition]) ? 1 : 2); if (PhoneticGroupsEng.TryGetValue(search.Text[searchPosition], out phoneticGroups)) resultWeight = Math.Min(resultWeight, phoneticGroups.Contains(source.Text[sourcePosition]) ? 1 : 2); return resultWeight; } private List<int> GetKeyCodes(List<KeyValuePair<char, int>> codeKeys, string word) { return word.ToLower().Select(ch => codeKeys.FirstOrDefault(ck => ck.Key == ch).Value).ToList(); } private string Transliterate(string text, Dictionary<char, string> cultureFrom) { IEnumerable<char> translateText = text.SelectMany(t => { string translateChar; if (cultureFrom.TryGetValue(t, out translateChar)) return translateChar; return t.ToString(); }); return string.Concat(translateText); } #region    static Dictionary<char, List<char>> PhoneticGroupsRus = new Dictionary<char, List<char>>(); static Dictionary<char, List<char>> PhoneticGroupsEng = new Dictionary<char, List<char>>(); #endregion static DistanceAlferov() { SetPhoneticGroups(PhoneticGroupsRus, new List<string>() { "", "", "", "", "", "", "" }); SetPhoneticGroups(PhoneticGroupsEng, new List<string>() { "aeiouy", "bp", "ckq", "dt", "lr", "mn", "gj", "fpv", "sxz", "csz" }); } private static void SetPhoneticGroups(Dictionary<char, List<char>> resultPhoneticGroups, List<string> phoneticGroups) { foreach (string group in phoneticGroups) foreach (char symbol in group) if (!resultPhoneticGroups.ContainsKey(symbol)) resultPhoneticGroups.Add(symbol, phoneticGroups.Where(pg => pg.Contains(symbol)).SelectMany(pg => pg).Distinct().Where(ch => ch != symbol).ToList()); } #region     /// <summary> ///    /// </summary> private static Dictionary<int, List<int>> DistanceCodeKey = new Dictionary<int, List<int>> { /* '`' */ { 192 , new List<int>(){ 49 }}, /* '1' */ { 49 , new List<int>(){ 50, 87, 81 }}, /* '2' */ { 50 , new List<int>(){ 49, 81, 87, 69, 51 }}, /* '3' */ { 51 , new List<int>(){ 50, 87, 69, 82, 52 }}, /* '4' */ { 52 , new List<int>(){ 51, 69, 82, 84, 53 }}, /* '5' */ { 53 , new List<int>(){ 52, 82, 84, 89, 54 }}, /* '6' */ { 54 , new List<int>(){ 53, 84, 89, 85, 55 }}, /* '7' */ { 55 , new List<int>(){ 54, 89, 85, 73, 56 }}, /* '8' */ { 56 , new List<int>(){ 55, 85, 73, 79, 57 }}, /* '9' */ { 57 , new List<int>(){ 56, 73, 79, 80, 48 }}, /* '0' */ { 48 , new List<int>(){ 57, 79, 80, 219, 189 }}, /* '-' */ { 189 , new List<int>(){ 48, 80, 219, 221, 187 }}, /* '+' */ { 187 , new List<int>(){ 189, 219, 221 }}, /* 'q' */ { 81 , new List<int>(){ 49, 50, 87, 83, 65 }}, /* 'w' */ { 87 , new List<int>(){ 49, 81, 65, 83, 68, 69, 51, 50 }}, /* 'e' */ { 69 , new List<int>(){ 50, 87, 83, 68, 70, 82, 52, 51 }}, /* 'r' */ { 82 , new List<int>(){ 51, 69, 68, 70, 71, 84, 53, 52 }}, /* 't' */ { 84 , new List<int>(){ 52, 82, 70, 71, 72, 89, 54, 53 }}, /* 'y' */ { 89 , new List<int>(){ 53, 84, 71, 72, 74, 85, 55, 54 }}, /* 'u' */ { 85 , new List<int>(){ 54, 89, 72, 74, 75, 73, 56, 55 }}, /* 'i' */ { 73 , new List<int>(){ 55, 85, 74, 75, 76, 79, 57, 56 }}, /* 'o' */ { 79 , new List<int>(){ 56, 73, 75, 76, 186, 80, 48, 57 }}, /* 'p' */ { 80 , new List<int>(){ 57, 79, 76, 186, 222, 219, 189, 48 }}, /* '[' */ { 219 , new List<int>(){ 48, 186, 222, 221, 187, 189 }}, /* ']' */ { 221 , new List<int>(){ 189, 219, 187 }}, /* 'a' */ { 65 , new List<int>(){ 81, 87, 83, 88, 90 }}, /* 's' */ { 83 , new List<int>(){ 81, 65, 90, 88, 67, 68, 69, 87, 81 }}, /* 'd' */ { 68 , new List<int>(){ 87, 83, 88, 67, 86, 70, 82, 69 }}, /* 'f' */ { 70 , new List<int>(){ 69, 68, 67, 86, 66, 71, 84, 82 }}, /* 'g' */ { 71 , new List<int>(){ 82, 70, 86, 66, 78, 72, 89, 84 }}, /* 'h' */ { 72 , new List<int>(){ 84, 71, 66, 78, 77, 74, 85, 89 }}, /* 'j' */ { 74 , new List<int>(){ 89, 72, 78, 77, 188, 75, 73, 85 }}, /* 'k' */ { 75 , new List<int>(){ 85, 74, 77, 188, 190, 76, 79, 73 }}, /* 'l' */ { 76 , new List<int>(){ 73, 75, 188, 190, 191, 186, 80, 79 }}, /* ';' */ { 186 , new List<int>(){ 79, 76, 190, 191, 222, 219, 80 }}, /* '\''*/ { 222 , new List<int>(){ 80, 186, 191, 221, 219 }}, /* 'z' */ { 90 , new List<int>(){ 65, 83, 88 }}, /* 'x' */ { 88 , new List<int>(){ 90, 65, 83, 68, 67 }}, /* 'c' */ { 67 , new List<int>(){ 88, 83, 68, 70, 86 }}, /* 'v' */ { 86 , new List<int>(){ 67, 68, 70, 71, 66 }}, /* 'b' */ { 66 , new List<int>(){ 86, 70, 71, 72, 78 }}, /* 'n' */ { 78 , new List<int>(){ 66, 71, 72, 74, 77 }}, /* 'm' */ { 77 , new List<int>(){ 78, 72, 74, 75, 188 }}, /* '<' */ { 188 , new List<int>(){ 77, 74, 75, 76, 190 }}, /* '>' */ { 190 , new List<int>(){ 188, 75, 76, 186, 191 }}, /* '?' */ { 191 , new List<int>(){ 190, 76, 186, 222 }}, }; /// <summary> ///     /// </summary> private static Dictionary<char, int> CodeKeysRus = new Dictionary<char, int> { { '' , 192 }, { '1' , 49 }, { '2' , 50 }, { '3' , 51 }, { '4' , 52 }, { '5' , 53 }, { '6' , 54 }, { '7' , 55 }, { '8' , 56 }, { '9' , 57 }, { '0' , 48 }, { '-' , 189 }, { '=' , 187 }, { '' , 81 }, { '' , 87 }, { '' , 69 }, { '' , 82 }, { '' , 84 }, { '' , 89 }, { '' , 85 }, { '' , 73 }, { '' , 79 }, { '' , 80 }, { '' , 219 }, { '' , 221 }, { '' , 65 }, { '' , 83 }, { '' , 68 }, { '' , 70 }, { '' , 71 }, { '' , 72 }, { '' , 74 }, { '' , 75 }, { '' , 76 }, { '' , 186 }, { '' , 222 }, { '' , 90 }, { '' , 88 }, { '' , 67 }, { '' , 86 }, { '' , 66 }, { '' , 78 }, { '' , 77 }, { '' , 188 }, { '' , 190 }, { '.' , 191 }, { '!' , 49 }, { '"' , 50 }, { '№' , 51 }, { ';' , 52 }, { '%' , 53 }, { ':' , 54 }, { '?' , 55 }, { '*' , 56 }, { '(' , 57 }, { ')' , 48 }, { '_' , 189 }, { '+' , 187 }, { ',' , 191 }, }; /// <summary> ///     /// </summary> private static Dictionary<char, int> CodeKeysEng = new Dictionary<char, int> { { '`', 192 }, { '1', 49 }, { '2', 50 }, { '3', 51 }, { '4', 52 }, { '5', 53 }, { '6', 54 }, { '7', 55 }, { '8', 56 }, { '9', 57 }, { '0', 48 }, { '-', 189 }, { '=', 187 }, { 'q', 81 }, { 'w', 87 }, { 'e', 69 }, { 'r', 82 }, { 't', 84 }, { 'y', 89 }, { 'u', 85 }, { 'i', 73 }, { 'o', 79 }, { 'p', 80 }, { '[', 219 }, { ']', 221 }, { 'a', 65 }, { 's', 83 }, { 'd', 68 }, { 'f', 70 }, { 'g', 71 }, { 'h', 72 }, { 'j', 74 }, { 'k', 75 }, { 'l', 76 }, { ';', 186 }, { '\'', 222 }, { 'z', 90 }, { 'x', 88 }, { 'c', 67 }, { 'v', 86 }, { 'b', 66 }, { 'n', 78 }, { 'm', 77 }, { ',', 188 }, { '.', 190 }, { '/', 191 }, { '~' , 192 }, { '!' , 49 }, { '@' , 50 }, { '#' , 51 }, { '$' , 52 }, { '%' , 53 }, { '^' , 54 }, { '&' , 55 }, { '*' , 56 }, { '(' , 57 }, { ')' , 48 }, { '_' , 189 }, { '+' , 187 }, { '{', 219 }, { '}', 221 }, { ':', 186 }, { '"', 222 }, { '<', 188 }, { '>', 190 }, { '?', 191 }, }; #endregion #region   /// <summary> ///   => ASCII (ISO 9-95) /// </summary> private static Dictionary<char, string> Translit_Ru_En = new Dictionary<char, string> { { '', "a" }, { '', "b" }, { '', "v" }, { '', "g" }, { '', "d" }, { '', "e" }, { '', "yo" }, { '', "zh" }, { '', "z" }, { '', "i" }, { '', "i" }, { '', "k" }, { '', "l" }, { '', "m" }, { '', "n" }, { '', "o" }, { '', "p" }, { '', "r" }, { '', "s" }, { '', "t" }, { '', "u" }, { '', "f" }, { '', "x" }, { '', "c" }, { '', "ch" }, { '', "sh" }, { '', "shh" }, { '', "" }, { '', "y" }, { '', "'" }, { '', "e" }, { '', "yu" }, { '', "ya" }, }; #endregion } 


Vitaly Alferov、2017

Source: https://habr.com/ru/post/J331174/


All Articles