配列をフィルタリングするファジー検索JavaScriptライブラリを探しています。 fuzzyset.js と Fuse.js を使用してみましたが、結果はひどいです(リンク先のページで試せるデモがあります)。
レーベンシュタインの距離を読んだ後、ユーザーがタイプするときに探しているものの近似としては良くない印象を受けます。知らない人のために、システムは挿入、削除、および置換の数を計算します2つの文字列を一致させるために必要です。
Levenshtein-Demerauモデルで修正されている1つの明らかな欠陥は、blubとboobの両方がbulb(それぞれが必要とする2つの置換)。ただし、bulbはblubよりもboobよりも似ていることは明らかであり、先ほど述べたモデルは転置。
テキスト補完のコンテキストでこれを使用したいので、配列['international', 'splint', 'tinder']
、および私のクエリはintです。前者のスコアは(より高いが)internationalはsplintよりも高いランクにすべきだと思います=悪い)10対後者の3。
だから私が探している(そして存在しない場合は作成する)のは、次のことを行うライブラリです:
誰かがこのような何かに出くわしましたか? StackOverflowはソフトウェアの推奨事項を要求する場所ではないことを認識していますが、上記の暗黙の(もう!ではありません!)は次のとおりです。
この件について good paper(pdf) を見つけました。いくつかのメモと抜粋:
アフィン編集距離関数は、挿入または削除のシーケンスに比較的低いコストを割り当てます
monger-Elkan距離関数(Monge&Elkan 1996)。これは、特定のコストパラメーターを備えたSmith-Waterman距離関数(Durban et al。1998)のアフィンバリアントです。
Smith-Waterman distance(wikipedia) 、「シーケンス全体を見る代わりに、Smith-Watermanアルゴリズムは、考えられるすべての長さのセグメントを比較し、類似性の尺度を最適化します。」それはn-gramアプローチです。
編集距離モデルに基づいていない、広く類似したメトリックは、Jaroメトリックです(Jaro 1995; 1989; Winkler 1999)。レコードリンケージの文献では、2つの文字列間の共通文字の数と順序に基づくこの方法の変形を使用して、良い結果が得られています。
Winkler(1999)によるこのバリアントは、最長共通プレフィックスの長さPも使用します
(主に短い文字列を対象としているようです)
テキスト補完の目的では、Monger-ElkanとJaro-Winklerのアプローチが最も理にかなっているようです。 JaroメトリックにWinklerが追加されたことで、単語の始まりがより重要になります。また、Monger-Elkanのアフィン性の側面は、Wordを完成させる必要性(これは単に追加のシーケンスである)が、Wordをあまり嫌わないことを意味します。
結論:
tFIDFランキングは、いくつかのトークンベースの距離メトリックの中で最高のパフォーマンスを発揮し、MongeとElkanによって提案された調整されたアフィンギャップ編集距離メトリックは、いくつかの文字列の編集距離メトリックの中で最高のパフォーマンスを発揮しました。驚くほど良い距離メトリックは、Jaroによって提案され、後にWinklerによって拡張された高速なヒューリスティックスキームです。これは、Monge-Elkanスキームとほぼ同じように機能しますが、1桁高速です。 TFIDFメソッドとJaro-Winklerを組み合わせる簡単な方法の1つは、TFIDFで使用される正確なトークン一致を、Jaro-Winklerスキームに基づいた近似トークン一致で置き換えることです。この組み合わせは、平均でJaro-WinklerまたはTFIDFのいずれよりもわずかに優れたパフォーマンスを発揮し、場合によってははるかに優れたパフォーマンスを発揮します。また、パフォーマンスは、このペーパーで検討したいくつかの最良のメトリックの学習された組み合わせに近いものです。
良い質問!しかし、私の考えでは、Levenshtein-Demerauを修正しようとするよりも、別のアルゴリズムを試すか、2つのアルゴリズムの結果を結合/重み付けする方が良いと考えています。
「開始接頭辞」に完全に一致するか、またはほぼ一致するものは、レーベンシュタイン-デメラウが特に重視していないものですが、ユーザーの明らかな期待はそうです。
「レーベンシュタインよりも良い」を検索したところ、とりわけ、次のことがわかりました。
http://www.joyofdata.de/blog/comparison-of-string-distance-algorithms/
これは、多くの「ストリング距離」測定に言及しています。要件に特に関連すると思われる3つは次のとおりです。
最長共通部分文字列距離:結果の部分文字列が同一になるまで両方の文字列で削除する必要があるシンボルの最小数。
q-gram distance:両方の文字列のN-gramベクトル間の絶対差の合計。
ジャカード距離:1は、共有N-gramと観測されたすべてのN-gramの商を計算します。
おそらく、これらのメトリックの重み付けされた組み合わせ(または最小)を、Levenshtein(共通のサブストリング、共通のN-gram、またはJaccardはすべて、similarストリングを強く好みます)またはJaccardを使用してみてください。
リスト/データベースのサイズによっては、これらのアルゴリズムは中程度の費用がかかる場合があります。実装したファジー検索では、構成可能な数のN-gramをDBからの "検索キー"として使用し、高価な文字列距離測定を実行して優先順位で並べ替えました。
SQLのファジー文字列検索に関するメモを書きました。見る:
Fuse.jsのような既存のファジーライブラリを使用してみましたが、ひどいものであることがわかったため、基本的に崇高な検索のように動作するライブラリを作成しました。 https://github.com/farzher/fuzzysort
許可される唯一のタイプミスは転置です。それは非常に堅実です(1kスター、0発行)、非常に高速で、ケースを簡単に処理します:
fuzzysort.go('int', ['international', 'splint', 'tinder'])
// [{highlighted: '*int*ernational', score: 10}, {highlighted: 'spl*int*', socre: 3003}]
これが私が数回使用したテクニックです...かなり良い結果が得られます。あなたが求めたすべてをしませんが。また、リストが大量の場合、これは高価になる可能性があります。
get_bigrams = (string) ->
s = string.toLowerCase()
v = new Array(s.length - 1)
for i in [0..v.length] by 1
v[i] = s.slice(i, i + 2)
return v
string_similarity = (str1, str2) ->
if str1.length > 0 and str2.length > 0
pairs1 = get_bigrams(str1)
pairs2 = get_bigrams(str2)
union = pairs1.length + pairs2.length
hit_count = 0
for x in pairs1
for y in pairs2
if x is y
hit_count++
if hit_count > 0
return ((2.0 * hit_count) / union)
return 0.0
2つの文字列をstring_similarity
に渡します。これは、類似度に応じて、0
と1.0
の間の数値を返します。この例ではLo-Dashを使用します
使用例....
query = 'jenny Jackson'
names = ['John Jackson', 'Jack Johnson', 'Jerry Smith', 'Jenny Smith']
results = []
for name in names
relevance = string_similarity(query, name)
obj = {name: name, relevance: relevance}
results.Push(obj)
results = _.first(_.sortBy(results, 'relevance').reverse(), 10)
console.log results
また、... フィドル
コンソールが開いているか、何も表示されないことを確認してください:)
atomの https://github.com/atom/fuzzaldrin/ libをご覧ください。
それはnpmで利用でき、シンプルなAPIを持ち、私のためにうまく働きました。
> fuzzaldrin.filter(['international', 'splint', 'tinder'], 'int');
< ["international", "splint"]
これは、ファジーマッチ用の私の短くコンパクトな関数です。
function fuzzyMatch(pattern, str) {
pattern = '.*' + pattern.split('').join('.*') + '.*';
const re = new RegExp(pattern);
return re.test(str);
}
(function (int) {
$("input[id=input]")
.on("input", {
sort: int
}, function (e) {
$.each(e.data.sort, function (index, value) {
if ( value.indexOf($(e.target).val()) != -1
&& value.charAt(0) === $(e.target).val().charAt(0)
&& $(e.target).val().length === 3 ) {
$("output[for=input]").val(value);
};
return false
});
return false
});
}(["international", "splint", "tinder"]))
Flookupという名前のGoogleスプレッドシートアドオンを確認し、この関数を使用します。
Flookup (lookupValue, tableArray, lookupCol, indexNum, threshold, [rank])
パラメーターの詳細は次のとおりです。
lookupValue
:検索している値tableArray
:検索するテーブルlookupCol
:検索する列indexNum
:データを返す列threshold
:データが返されるべきでない類似度のパーセンテージrank
:n番目のベストマッチ(つまり、最初のマッチがあなたの好みに合わない場合)これはあなたの要件を満たすはずです...私はポイント番号2についてはわかりませんが。
公式ウェブサイト で詳細をご覧ください。