ユーザーがGoogleで検索語を入力するときにクエリの候補を提供するために使用されるアルゴリズムについて言及しています。
主に興味があるのは:1.最も重要な結果(一致するものよりもクエリである可能性が高い)2.部分文字列に一致する3.あいまい一致
Trieまたは一般化されたtrieを使用して一致を見つけることができますが、上記の要件を満たしていません...
以前に尋ねた同様の質問 here
(へん)素晴らしいファジー/部分文字列マッチングアルゴリズムについては、Damn Cool Algorithmsをご覧ください。
これらは試行に取って代わるものではなく、試行におけるブルートフォースルックアップを防止します-これは依然として大きな勝利です。次に、おそらくトライのサイズを制限する方法が必要です。
最後に、可能な限りルックアップを防止したい...
ただ言いたいのですが...この問題の良い解決策は、Ternary Search Tree以上のものを組み込むことです。 Ngrams、および帯状疱疹(フレーズ)が必要です。単語境界エラーも検出する必要があります。 「hell o」は「hello」で、「whitesocks」は「white socks」です。これらは前処理ステップです。データを適切に前処理しないと、貴重な検索結果が得られません。三項検索ツリーは、Wordとは何かを理解するのに役立つコンポーネントであり、入力されたWordがインデックス内の有効なWordでない場合に関連する単語の推測を実装するのにも役立ちます。
Googleアルゴリズムは、フレーズの提案と修正を実行します。 Googleアルゴリズムにはコンテキストの概念もあります...最初に検索する単語が天気関連であり、それらを「weatherforcst」vs「monsoonfrcst」vs「deskfrcst」と組み合わせた場合-私の推測は舞台裏でランキングが変更されている最初に出会った単語に基づいた提案-予測と天気は関連する単語であるため、予測はDid-You-Mean推測で上位になります。
単語の部分(ngram)、フレーズ用語(シングル)、単語の近接(Wordクラスタリングインデックス)、三項検索ツリー(Wordルックアップ)。
Googleの正確なアルゴリズムは不明ですが、 と言われています ユーザー入力の統計分析で機能します。ほとんどの場合に適さないアプローチ。より一般的な自動補完は、次のいずれかを使用して実装されます。
completely 、a Java後者の概念のいくつかを実装するオートコンプリートライブラリをご覧ください。
soundex や levenshtein distance などのツールがあり、特定の範囲内のあいまい一致を見つけるために使用できます。
Soundexは似た音の単語を検索し、levenshtein distanceは別のWordから特定の編集距離内にある単語を検索します。
FirefoxのAwesomeバーアルゴリズム をご覧ください
Googleは、何百万もの人気のあるクエリと過去の関連クエリを考慮に入れるため、便利です。
しかし、それは良い補完アルゴリズム/ UIを持っていません:
Tomcat tut
->「Tomcatチュートリアル」を正しく提案します。 Tomcat rial
->提案なし)-::部分文字列とあいまい一致の場合、レーベンシュタイン距離アルゴリズムはかなりうまく機能しました。オートコンプリート/提案の業界実装ほど完璧ではないように思えますが。 GoogleとMicrosoftのIntellisenseはどちらも優れた仕事をしています。なぜなら、この基本的なアルゴリズムを改良して、異種の文字列を一致させるために必要な編集操作の種類を比較検討しているからです。例えば。 2つの文字の転置は、おそらく2(挿入と削除)ではなく、1つの操作としてのみカウントする必要があります。
しかし、それでもこれで十分だと思います。 C#での実装です...
// This is the traditional Levenshtein Distance algorithem, though I've tweaked it to make
// it more like Google's autocomplete/suggest. It returns the number of operations
// (insert/delete/substitute) required to change one string into another, with the
// expectation that userTyped is only a partial version of fullEntry.
// Gives us a measurement of how similar the two strings are.
public static int EditDistance(string userTyped, string fullEntry)
{
if (userTyped.Length == 0) // all entries are assumed to be fully legit possibilities
return 0; // at this point, because the user hasn't typed anything.
var inx = fullEntry.IndexOf(userTyped[0]);
if (inx < 0) // If the 1st character doesn't exist anywhere in the entry, it's not
return Int32.MaxValue; // a possible match.
var lastInx = inx;
var lastMatchCount = 0;
TryAgain:
// Is there a better starting point?
var len = fullEntry.Length - inx;
var matchCount = 1;
var k = 1;
for (; k < len; k++)
{
if (k == userTyped.Length || userTyped[k] != fullEntry[k + inx])
{
if (matchCount > lastMatchCount)
{
lastMatchCount = matchCount;
lastInx = inx;
}
inx = fullEntry.IndexOf(userTyped[0], inx + 1);
matchCount = 0;
if (inx > 0)
goto TryAgain;
else
break;
}
else
matchCount++;
}
if (k == len && matchCount > lastMatchCount)
lastInx = inx;
if (lastInx > 0)
fullEntry = fullEntry.Substring(lastInx); // Jump to 1st character match, ignoring previous values
// The start of the Levenshtein Distance algorithem.
var m = userTyped.Length;
var n = Math.Min(m, fullEntry.Length);
int[,] d = new int[m + 1, n + 1]; // "distance" - meaning number of operations.
for (var i = 0; i <= m; i++)
d[i, 0] = i; // the distance of any first string to an empty second string
for (var j = 0; j <= n; j++)
d[0, j] = j; // the distance of any second string to an empty first string
for (var j = 1; j <= n; j++)
for (var i = 1; i <= m; i++)
if (userTyped[i - 1] == fullEntry[j - 1])
d[i, j] = d[i - 1, j - 1]; // no operation required
else
d[i, j] = Math.Min
(
d[i - 1, j] + 1, // a deletion
Math.Min(
d[i, j - 1] + 1, // an insertion
d[i - 1, j - 1] + 1 // a substitution
)
);
return d[m, n];
}
問題の全体的なデザインを探している場合は、 https://www.interviewbit.com/problems/search-typeahead/ でコンテンツを読んでみてください。
トライを使用する単純なアプローチでオートコンプリートを構築することから始めて、それを基に構築します。また、特定のユースケースに対応するためのサンプリングやオフライン更新などの最適化手法についても説明します。
ソリューションの拡張性を維持するには、トライデータをインテリジェントに分割する必要があります。
完全に異なるデータ構造を追求するよりも、専門的なトライを構築する方がよいと思います。
その機能は、各葉に対応するWordの検索頻度を反映するフィールドがあるトライで明らかになりました。
検索クエリメソッドは、各子孫リーフノードまでの距離に各子孫リーフノードに関連付けられた検索頻度を乗算して計算された最大値を持つ子孫リーフノードを表示します。
Googleが使用するデータ構造(およびその結果としてのアルゴリズム)はおそらく非常に複雑であり、特定のアカウント(および時刻...および天気...季節)からの検索頻度など、他の多数の要因を潜在的に取り入れています...および月相...および...)。ただし、基本的なトライデータ構造は、各ノードにフィールドを追加し、検索クエリメソッドでそれらのフィールドを使用することで、あらゆる種類の特殊な検索設定に拡張できると考えています。