意味のあるJavaScriptのあいまい検索


98

配列をフィルタリングするためのあいまい検索JavaScriptライブラリを探しています。fuzzyset.jsfuse.jsを使用してみましたが、結果はひどいです(リンクされたページで試すことができるデモがあります)。

レーベンシュタイン距離について少し読んだ後、それはユーザーがタイプしたときに探しているものの不十分な近似として私に印象づけます。知らない人のために、システムは、2つの文字列を一致させるために必要な挿入削除、および置換の数を計算します。

Levenshtein-Demerauモデルで修正された明らかな欠陥の1つは、blubboobの両方が球根に等しく類似していると見なされることです(それぞれ2つの置換が必要です)。ただし、bulbboobよりもblubに似ていることは明らかです。先ほど述べたモデルでは、転置を許可することでそれを認識しています。

これをテキスト補完のコンテキストで使用したいので、配列['international', 'splint', 'tinder']があり、クエリがintの場合、前者のスコア(higher = worse)が10であっても、splintよりもInternationalのランクが高くなるはずです。後者の対3。

だから私が探しているのは(そしてそれが存在しない場合は作成します)、次のことを行うライブラリです:

  • さまざまなテキスト操作に重みを付ける
  • 各操作の重み付けは、単語のどこに出現するかによって異なります(初期の操作は後の操作よりもコストがかかります)。
  • 関連性でソートされた結果のリストを返します

誰かがこのようなものに出くわしましたか?StackOverflowがソフトウェアの推奨事項を求める場所ではないことに気づきましたが、上記の暗黙の(もうない!)は次のとおりです。これを正しい方法で考えていますか?


編集する

この件に関して良い論文(pdf)を見つけました。いくつかのメモと抜粋:

アフィン編集距離関数は、挿入または削除のシーケンスに比較的低いコストを割り当てます

Monger-Elkan距離関数(Monge&Elkan 1996)。これは、特定のコストパラメータを持つSmith-Waterman距離関数(Durban et al。1998)のアフィンバリアントです。

ためのスミス-ウォーターマン距離(ウィキペディア)、「その代わりに全配列を見てから、スミス-ウォーターマンアルゴリズムは、すべての可能な長さのセグメントを比較し、類似性測度を最適化します。」それはn-gramアプローチです。

編集距離モデルに基づいていない、広く類似したメトリックは、Jaroメトリックです(Jaro 1995; 1989; Winkler 1999)。レコードリンケージの文献では、2つの文字列間の共通文字の数と順序に基づく、このメソッドのバリアントを使用して良好な結果が得られています。

Winkler(1999)によるこのバリアントも、最長の共通プレフィックスの長さPを使用します

(主に短い文字列を対象としているようです)

テキスト補完の目的で、Monger-ElkanおよびJaro-Winklerのアプローチが最も理にかなっているようです。WinklerのJaroメトリックへの追加は、単語の始まりをより重く効果的に重み付けします。また、Monger-Elkanのアフィン的側面は、単語を補完する必要性(これは単なる追加のシーケンスです)がそれほど嫌われないことを意味します。

結論:

TFIDFランキングは、いくつかのトークンベースの距離メトリックの間で最もよく機能し、MongeとElkanによって提案された調整されたアフィンギャップ編集距離メトリックは、いくつかの文字列編集距離メトリックの間で最もよく機能しました。驚くほど良い距離メトリックは、Jaroによって提案され、後でWinklerによって拡張された高速ヒューリスティックスキームです。これは、Monge-Elkanスキームとほぼ同じように機能しますが、桁違いに高速です。TFIDFメソッドとJaro-Winklerを組み合わせる簡単な方法の1つは、TFIDFで使用されている正確なトークンの一致を、Jaro-Winklerスキームに基づくおおよそのトークンの一致に置き換えることです。この組み合わせは、平均でJaro-WinklerやTFIDFよりもわずかに優れており、場合によってははるかに優れています。また、このペーパーで検討されているいくつかの最良のメトリックの学習された組み合わせに近いパフォーマンスを発揮します。


すばらしい質問です。私は同じようなことをしようとしていますが、同じ文字列比較の考慮事項があります。文字列比較のJavaScript実装を見つけてビルドしたことはありますか?ありがとう。
ニコラス2014

1
@nicholas私はgithubでfuzzyset.jsを単純にforkして小さなクエリ文字列を説明しましたが、重み付けされた文字列操作は説明していませんが、意図した文字列補完のアプリケーションにはかなり良い結果が得られます。レポを
willlma 2014

ありがとう。私はそれを試してみます。この文字列比較関数:github.com/zdyn/jaro-winkler-jsも見つかりました。かなりうまくいくようです。
ニコラス2014

1
これを試してください:subtexteditor.github.io/fuzzysearch.js
マイケル

1
@michaeldayこれはタイプミスを考慮していません。デモでは、タイピングkroleは戻りませんがFinal Fantasy V: Krile、希望します。クエリ内のすべての文字が結果で同じ順序で存在する必要がありますが、これはかなり近視眼的です。あいまい検索を適切に行う唯一の方法は、一般的なタイプミスのデータベースを用意することです。
willlma 2015年

回答:


21

良い質問!しかし、私の考えでは、レーベンシュタイン-デメラウを変更しようとするよりも、別のアルゴリズムを試すか、2つのアルゴリズムの結果を組み合わせて重み付けするほうがよいかもしれません。

「開始プレフィックス」との完全一致または近似一致はLevenshtein-Demerauが特に重要視していないものですが、ユーザーの期待は明白です。

私は「レーヴェンシュタインより良い」を検索し、とりわけ、これを見つけました:

http://www.joyofdata.de/blog/comparison-of-string-distance-algorithms/

これは、いくつかの「ストリング距離」の測定値に言及しています。要件に特に関連すると思われる3つは次のとおりです。

  1. 最長共通部分文字列距離:結果の部分文字列が同一になるまで両方の文字列で削除する必要がある記号の最小数。

  2. q-gram距離:両方の文字列のN-gramベクトル間の絶対差の合計。

  3. ジャカード距離: 1は、共有されたN-gramと観測されたすべてのN-gramの商を1分減らします。

たぶん、これらのメトリックの重み付けされた組み合わせ(または最小値)をLevenshteinと一緒に使用することができます-一般的なサブストリング、一般的なN-gramまたはJaccardはすべて同様に強く優先しますストリングをます-あるいはおそらくJaccardだけを使用してみますか?

リスト/データベースのサイズによっては、これらのアルゴリズムは適度に高価になる可能性があります。私が実装したファジー検索では、構成可能な数のNグラムをDBからの「検索キー」として使用し、高価な文字列距離メジャーを実行して優先順位でソートしました。

SQLでのファジー文字列検索に関するメモを書きました。見る:


63

私は、fuse.jsのような既存のファジーライブラリを使用してみましたが、ひどいものもあるので、基本的には崇高な検索のように動作するライブラリを作成しました。https://github.com/farzher/fuzzysort

それが許可する唯一のタイプミスは転置です。非常に安定しており(1つ星、0問)非常に高速で、ケースを簡単に処理できます。

fuzzysort.go('int', ['international', 'splint', 'tinder'])
// [{highlighted: '*int*ernational', score: 10}, {highlighted: 'spl*int*', socre: 3003}]


4
私はFuse.jsに不満があり、ライブラリを試してみました-うまくいきました!よくできました:)
dave

1
私が直面したこのライブラリの唯一の問題は、単語が完全であるが、たとえばスペルが間違っている場合です。たとえば、正しい単語が「XRP」で、「XRT」を検索した場合、スコアが得られません
PirateApp

1
@PirateAppうん、スペルミスは処理しません(sublimeの検索は処理しないため)。人々が不満を言っているので、私はこれをちょっと調べています。この検索がgithubの問題として失敗する使用例の例を教えてください
Farzher

3
このlibについて不思議に思っている方のために、スペルチェックも実装されました!私はこのライブラリをfusejsやその他よりもお勧めします
PirateApp

1
@ user4815162342自分でコーディングする必要があります。このスレッドをチェックアウトしてください。コードサンプルgithub.com/farzher/fuzzysort/issues/19
Farzher

19

これは私が数回使用したテクニックです...それはかなり良い結果を与えます。ただし、要求したすべてのことを行うわけではありません。また、リストが膨大な場合、これは高価になる可能性があります。

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

とのstring_similarity間の数値を返す2つの文字列を渡します01.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

また... フィドルを持っています

コンソールが開いていること、または何も表示されないことを確認してください。


3
ありがとう、それがまさに私が探していたものです。それが普通のjsだった場合にのみ、それはより良いでしょう;)
lucaswxp

1
function get_bigrams(string){var s = string.toLowerCase()var v = s.split( ''); for(var i = 0; i <v.length; i ++){v [i] = s.slice(i、i + 2); } return v; } function string_similarity(str1、str2){if(str1.length> 0 && str2.length> 0){var pair1 = get_bigrams(str1); var pair2 = get_bigrams(str2); var union = pairs1.length + pairs2.length; var hits = 0; for(var x = 0; x <pairs1.length; x ++){for(var y = 0; y <pairs2.length; y ++){if(pairs1 [x] == pairs2 [y])hit_count ++; }} if(hits> 0)return((2.0 * hits)/ union); } 0.0を返します}
jaya

複数のキーで検索したいオブジェクトでこれをどのように使用しますか?
user3808307

これにはいくつかの問題があります:1)文字列の最初と最後の文字にアンダーウェイトを付けます。2)バイグラムの比較はO(n ^ 2)です。3)実装により、類似性スコアは1を超える場合があります。これは明らかに意味がありません。以下の私の回答でこれらすべての問題を修正します。
MgSam

8

これはファジーマッチのための私の短くてコンパクトな関数です:

function fuzzyMatch(pattern, str) {
  pattern = '.*' + pattern.split('').join('.*') + '.*';
  const re = new RegExp(pattern);
  return re.test(str);
}

ほとんどの場合、あなたが望むものではないかもしれませんが、それはまさに私のためでした。
schmijos


2

2019年11月の更新。私はヒューズがかなりまともなアップグレードを持っていることを発見しました。ただし、ブール(OR、ANDなどの演算子)を使用することも、API検索インターフェイスを使用して結果をフィルター処理することもできませんでした。

私が発見したnextapps-de/flexsearchhttps : //github.com/nextapps-de/flexsearchそして私が試した他の多くのjavascript検索ライブラリをはるかに上回っていると信じています、そしてそれはサポートしていますboolます。

検索データ(ストレージなど)のJavaScriptオブジェクトのリストを入力できます。APIは十分に文書化されています。 。https //github.com/nextapps-de/flexsearch#api-overview

これまでのところ、1万件近くのレコードにインデックスを付けており、私の検索はすぐ近くにあります。つまり、各検索の目立たない時間。


2

@InternalFXが提供するソリューションを以下に示しますが、JSで使用しています(共有するために使用しました)。

function get_bigrams(string){
  var s = string.toLowerCase()
  var v = s.split('');
  for(var i=0; i<v.length; i++){ v[i] = s.slice(i, i + 2); }
  return v;
}

function string_similarity(str1, str2){
  if(str1.length>0 && str2.length>0){
    var pairs1 = get_bigrams(str1);
    var pairs2 = get_bigrams(str2);
    var union = pairs1.length + pairs2.length;
    var hits = 0;
    for(var x=0; x<pairs1.length; x++){
      for(var y=0; y<pairs2.length; y++){
        if(pairs1[x]==pairs2[y]) hits++;
    }}
    if(hits>0) return ((2.0 * hits) / union);
  }
  return 0.0
}

2

InternalFxによるCoffeeScriptバイグラムソリューションの問題を修正し、それを一般的なnグラムソリューションにしました(グラムのサイズはカスタマイズできます)。

これはTypeScriptですが、型注釈を削除することができ、通常のJavaScriptとしても正常に機能します。

/**
 * Compares the similarity between two strings using an n-gram comparison method. 
 * The grams default to length 2.
 * @param str1 The first string to compare.
 * @param str2 The second string to compare.
 * @param gramSize The size of the grams. Defaults to length 2.
 */
function stringSimilarity(str1: string, str2: string, gramSize: number = 2) {
  function getNGrams(s: string, len: number) {
    s = ' '.repeat(len - 1) + s.toLowerCase() + ' '.repeat(len - 1);
    let v = new Array(s.length - len + 1);
    for (let i = 0; i < v.length; i++) {
      v[i] = s.slice(i, i + len);
    }
    return v;
  }

  if (!str1?.length || !str2?.length) { return 0.0; }

  //Order the strings by length so the order they're passed in doesn't matter 
  //and so the smaller string's ngrams are always the ones in the set
  let s1 = str1.length < str2.length ? str1 : str2;
  let s2 = str1.length < str2.length ? str2 : str1;

  let pairs1 = getNGrams(s1, gramSize);
  let pairs2 = getNGrams(s2, gramSize);
  let set = new Set<string>(pairs1);

  let total = pairs2.length;
  let hits = 0;
  for (let item of pairs2) {
    if (set.delete(item)) {
      hits++;
    }
  }
  return hits / total;
}

例:

console.log(stringSimilarity("Dog", "Dog"))
console.log(stringSimilarity("WolfmanJackIsDaBomb", "WolfmanJackIsDaBest"))
console.log(stringSimilarity("DateCreated", "CreatedDate"))
console.log(stringSimilarity("a", "b"))
console.log(stringSimilarity("CreateDt", "DateCreted"))
console.log(stringSimilarity("Phyllis", "PyllisX"))
console.log(stringSimilarity("Phyllis", "Pylhlis"))
console.log(stringSimilarity("cat", "cut"))
console.log(stringSimilarity("cat", "Cnut"))
console.log(stringSimilarity("cc", "Cccccccccccccccccccccccccccccccc"))
console.log(stringSimilarity("ab", "ababababababababababababababab"))
console.log(stringSimilarity("a whole long thing", "a"))
console.log(stringSimilarity("a", "a whole long thing"))
console.log(stringSimilarity("", "a non empty string"))
console.log(stringSimilarity(null, "a non empty string"))

TypeScript Playgroundでお試しください


0
(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"]))

jsfiddle http://jsfiddle.net/guest271314/QP7z5/


0

FlookupというGoogleスプレッドシートアドオンを確認し、次の関数を使用してください。

Flookup (lookupValue, tableArray, lookupCol, indexNum, threshold, [rank])

パラメータの詳細は次のとおりです。

  1. lookupValue:検索している値
  2. tableArray:検索するテーブル
  3. lookupCol:検索する列
  4. indexNum:データを返す列
  5. threshold:データを返してはならない類似性の割合
  6. rank:n番目の一致(つまり、最初の一致が好みに合わない場合)

これは要件を満たすはずです...ポイント2についてはわかりませんが。

詳細は公式ウェブサイトをご覧ください

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.