最も近い文字列の一致を取得する


397

複数の文字列をテスト文字列と比較し、それによく似た文字列を返す方法が必要です。

TEST STRING: THE BROWN FOX JUMPED OVER THE RED COW

CHOICE A   : THE RED COW JUMPED OVER THE GREEN CHICKEN
CHOICE B   : THE RED COW JUMPED OVER THE RED COW
CHOICE C   : THE RED FOX JUMPED OVER THE BROWN COW

(これを正しく行った場合)「テスト文字列」に最も近い文字列は「選択C」になります。これを行う最も簡単な方法は何ですか?

これをVB.net、Lua、JavaScriptを含む複数の言語に実装する予定です。この時点で、疑似コードは受け入れられます。特定の言語の例を提供できれば、これもありがたいです!


3
通常、この種のことを行うアルゴリズムは、検査された文字列をターゲット文字列に変換するために必要な変更の数を決定します。これらのタイプのアルゴリズムは、このような状況ではまったくうまく機能しません。コンピューターでこれを実現するのは大変だと思います。
Matt Greer、

3
多くの言語でのレーベンシュタイン距離のソースコード:ジャワ、ルビーやPython、PHPなどen.wikibooks.org/wiki/Algorithm_Implementation/Strings/...
joelparkerhenderson

9
一般に、「最も近い文字列」として数えられるのは、使用される類似性測定と、配置にギャップを導入するために使用されるペナルティによって異なります。たとえば、「牛」と「鶏」は「牛」と「赤」よりも類似していると考えますか(それらは関連する概念であるため)、それとも逆ですか(「鶏」には「牛」よりも多くの文字があるため) )?しかし、類似性の尺度とギャップのペナルティを考えると、以下のレーベンシュタインアルゴリズムが最も近い文字列を見つけることが保証されていることを示すことができます。同じことが、Needleman-WunschとSmith-Watermanにも当てはまります(以下を参照)。
Sten L

文字のグループ化、または単語のグループ化を行います。スコアを付けます。
Casey

回答:


952

その他の情報のデータベースで石油リグに関するユーザー入力情報を検索することになると、約1年前にこの問題が発生しました。目標は、最も一般的な要素を持つデータベースエントリを特定できる、ある種のあいまい文字列検索を実行することでした。

レーベンシュタイン距離を実装することを含む研究の一部アルゴリズムの。これは、文字列またはフレーズを別の文字列またはフレーズに変換するために、いくつの変更を行う必要があるかを決定します。

私が思いついた実装は比較的単純で、2つのフレーズの長さ、各フレーズ間の変更の数、およびターゲットエントリで各単語が見つかるかどうかを加重比較しました。

記事は非公開サイトにあるので、ここに関連コンテンツを追加できるように最善を尽くします。


ファジー文字列マッチングは、2つの単語またはフレーズの類似性を人間のように推定するプロセスです。多くの場合、相互に最も類似している単語やフレーズを特定する必要があります。この記事では、ファジー文字列マッチングの問題に対する社内ソリューションと、以前は面倒なユーザーの関与を必要としたタスクを自動化できるさまざまな問題の解決におけるその有用性について説明します。

前書き

メキシコ湾の検証ツールの開発中に、最初はファジー文字列照合を行う必要がありました。存在していたのは、メキシコ湾の既知の石油掘削装置とプラットフォームのデータベースでした。保険を購入する人々は、資産について間違って入力された情報をいくつか提供し、既知のプラットフォームのデータベースと照合する必要がありました。提供された情報が非常に少ない場合、私たちができる最善のことは、引受人が参照していたものを「認識」して適切な情報を呼び出すことです。この場合、この自動化されたソリューションが役に立ちます。

私は1日かけてファジー文字列マッチングの方法を研究し、最終的にはWikipediaの非常に便利なレーベンシュタイン距離アルゴリズムに出会いました。

実装

その背後にある理論について読んだ後、私はそれを実装して最適化する方法を見つけました。これは私のコードがVBAでどのように見えるかです:

'Calculate the Levenshtein Distance between two strings (the number of insertions,
'deletions, and substitutions needed to transform the first string into the second)
Public Function LevenshteinDistance(ByRef S1 As String, ByVal S2 As String) As Long
    Dim L1 As Long, L2 As Long, D() As Long 'Length of input strings and distance matrix
    Dim i As Long, j As Long, cost As Long 'loop counters and cost of substitution for current letter
    Dim cI As Long, cD As Long, cS As Long 'cost of next Insertion, Deletion and Substitution
    L1 = Len(S1): L2 = Len(S2)
    ReDim D(0 To L1, 0 To L2)
    For i = 0 To L1: D(i, 0) = i: Next i
    For j = 0 To L2: D(0, j) = j: Next j

    For j = 1 To L2
        For i = 1 To L1
            cost = Abs(StrComp(Mid$(S1, i, 1), Mid$(S2, j, 1), vbTextCompare))
            cI = D(i - 1, j) + 1
            cD = D(i, j - 1) + 1
            cS = D(i - 1, j - 1) + cost
            If cI <= cD Then 'Insertion or Substitution
                If cI <= cS Then D(i, j) = cI Else D(i, j) = cS
            Else 'Deletion or Substitution
                If cD <= cS Then D(i, j) = cD Else D(i, j) = cS
            End If
        Next i
    Next j
    LevenshteinDistance = D(L1, L2)
End Function

シンプルでスピーディー、そして非常に便利なメトリック。これを使用して、2つの文字列の類似性を評価するための2つの個別のメトリックを作成しました。1つは「valuePhrase」、もう1つは「valueWords」です。valuePhraseは、2つのフレーズ間のレーベンシュタイン距離であり、valueWordsは、スペース、ダッシュなどの区切り文字に基づいて文字列を個々の単語に分割し、各単語を他の単語と比較して、最短で合計します任意の2つの単語を結ぶレーベンシュタイン距離。基本的に、これは、単語ごとの順列と同様に、ある「フレーズ」の情報が実際に別の「フレーズ」に含まれているかどうかを測定します。デリミタに基づいて文字列を分割する最も効率的な方法を考え出すサイドプロジェクトとして数日を費やしました。

valueWords、valuePhrase、およびSplit関数:

Public Function valuePhrase#(ByRef S1$, ByRef S2$)
    valuePhrase = LevenshteinDistance(S1, S2)
End Function

Public Function valueWords#(ByRef S1$, ByRef S2$)
    Dim wordsS1$(), wordsS2$()
    wordsS1 = SplitMultiDelims(S1, " _-")
    wordsS2 = SplitMultiDelims(S2, " _-")
    Dim word1%, word2%, thisD#, wordbest#
    Dim wordsTotal#
    For word1 = LBound(wordsS1) To UBound(wordsS1)
        wordbest = Len(S2)
        For word2 = LBound(wordsS2) To UBound(wordsS2)
            thisD = LevenshteinDistance(wordsS1(word1), wordsS2(word2))
            If thisD < wordbest Then wordbest = thisD
            If thisD = 0 Then GoTo foundbest
        Next word2
foundbest:
        wordsTotal = wordsTotal + wordbest
    Next word1
    valueWords = wordsTotal
End Function

''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' SplitMultiDelims
' This function splits Text into an array of substrings, each substring
' delimited by any character in DelimChars. Only a single character
' may be a delimiter between two substrings, but DelimChars may
' contain any number of delimiter characters. It returns a single element
' array containing all of text if DelimChars is empty, or a 1 or greater
' element array if the Text is successfully split into substrings.
' If IgnoreConsecutiveDelimiters is true, empty array elements will not occur.
' If Limit greater than 0, the function will only split Text into 'Limit'
' array elements or less. The last element will contain the rest of Text.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function SplitMultiDelims(ByRef Text As String, ByRef DelimChars As String, _
        Optional ByVal IgnoreConsecutiveDelimiters As Boolean = False, _
        Optional ByVal Limit As Long = -1) As String()
    Dim ElemStart As Long, N As Long, M As Long, Elements As Long
    Dim lDelims As Long, lText As Long
    Dim Arr() As String

    lText = Len(Text)
    lDelims = Len(DelimChars)
    If lDelims = 0 Or lText = 0 Or Limit = 1 Then
        ReDim Arr(0 To 0)
        Arr(0) = Text
        SplitMultiDelims = Arr
        Exit Function
    End If
    ReDim Arr(0 To IIf(Limit = -1, lText - 1, Limit))

    Elements = 0: ElemStart = 1
    For N = 1 To lText
        If InStr(DelimChars, Mid(Text, N, 1)) Then
            Arr(Elements) = Mid(Text, ElemStart, N - ElemStart)
            If IgnoreConsecutiveDelimiters Then
                If Len(Arr(Elements)) > 0 Then Elements = Elements + 1
            Else
                Elements = Elements + 1
            End If
            ElemStart = N + 1
            If Elements + 1 = Limit Then Exit For
        End If
    Next N
    'Get the last token terminated by the end of the string into the array
    If ElemStart <= lText Then Arr(Elements) = Mid(Text, ElemStart)
    'Since the end of string counts as the terminating delimiter, if the last character
    'was also a delimiter, we treat the two as consecutive, and so ignore the last elemnent
    If IgnoreConsecutiveDelimiters Then If Len(Arr(Elements)) = 0 Then Elements = Elements - 1

    ReDim Preserve Arr(0 To Elements) 'Chop off unused array elements
    SplitMultiDelims = Arr
End Function

類似性の測定

これらの2つのメトリックと、2つの文字列間の距離を単純に計算する3番目のメトリックを使用して、最適化アルゴリズムを実行して最大数の一致を達成できる一連の変数があります。ファジー文字列マッチングは、それ自体がファジーサイエンスであるため、文字列の類似性を測定するための線形に独立したメトリックを作成し、互いに一致させたい既知の文字列のセットを持つことで、特定のスタイルのパラメーターを見つけることができます文字列、最高のあいまい一致結果を与えます。

当初、このメトリックの目標は、完全一致の場合は検索値を低くし、順列が増えるほど検索値を増やすことでした。非現実的なケースでは、これは一連の明確に定義された順列を使用して定義し、必要に応じて検索値の結果が増加するように最終的な式を設計することはかなり簡単でした。

ファジー文字列マッチング順列

上のスクリーンショットでは、ヒューリスティックを微調整して、検索語と結果の違いを認識できるように、適切にスケーリングされていると感じられるものを考え出しました。Value Phrase上記のスプレッドシートで使用したヒューリスティックはでした=valuePhrase(A2,B2)-0.8*ABS(LEN(B2)-LEN(A2))。2つの「フレーズ」の長さの違いの80%だけ、レベンシュタイン距離のペナルティを効果的に減らしていました。このように、同じ長さの「フレーズ」は完全なペナルティを受けますが、「追加情報」(長い)を含むが、それでもなお、ほとんど同じ文字を共有する「フレーズ」はペナルティが軽減されます。Value Words関数をそのまま使用し、最後にSearchVal的なヒューリスティックを次のように定義しました=MIN(D2,E2)*0.8+MAX(D2,E2)*0.2-加重平均。2つのスコアのうち低い方の方が80%、高い方のスコアの20%が加重されました。これは、適切な一致率を得るための私のユースケースに適したヒューリスティックでした。これらの重みは、テストデータとの最適な一致率を取得するために調整できるものです。

ファジー文字列マッチング値フレーズ

ファジー文字列マッチング値ワード

ご覧のとおり、あいまいな文字列マッチングメトリックである最後の2つのメトリックは、一致することを意図した文字列(対角線の下)に低いスコアを与える自然な傾向があります。これはとてもいい。

アプリケーション あいまい一致の最適化を可能にするために、各メトリックに重みを付けます。そのため、ファジー文字列照合のすべてのアプリケーションは、異なる方法でパラメーターに重みを付けることができます。最終スコアを定義する式は、メトリックとその重みの単純な組み合わせです。

value = Min(phraseWeight*phraseValue, wordsWeight*wordsValue)*minWeight
      + Max(phraseWeight*phraseValue, wordsWeight*wordsValue)*maxWeight
      + lengthWeight*lengthValue

最適化アルゴリズム(ニューラルネットワークは離散的な多次元問題であるため、ここでは最適です)を使用して、目標は一致の数を最大化することです。この最終的なスクリーンショットに示すように、各セットの正しい一致の数を検出する関数を作成しました。最低スコアに一致するはずの文字列が割り当てられている場合、列または行にポイントが割り当てられ、最低スコアに同点があり、関連する一致した文字列が正しい一致である場合、部分的なポイントが与えられます。次に、それを最適化しました。緑のセルが現在の行に最も一致する列であり、セルの周りの青い四角が現在の列に最も一致する行であることがわかります。下隅のスコアはおおよその一致数であり、これが最大化するために最適化問題に伝えているものです。

ファジー文字列マッチング最適化メトリック

アルゴリズムは素晴らしい成功でした、そして、解決パラメーターはこのタイプの問題について多くを言います。最適化されたスコアは44で、最高のスコアは48です。最後の5列はおとりであり、行の値とまったく一致していません。デコイが多いほど、自然にベストマッチを見つけることが難しくなります。

この特定の一致の場合、長い単語を表す略語を想定しているため、文字列の長さは関係ありません。したがって、長さの最適な重みは-0.3です。つまり、長さが異なる文字列にペナルティを課しません。これらの略語を見込んでスコアを下げ、文字列が短いために置換が少なくて済む非単語の一致よりも部分的な単語の一致を優先する余地を増やしています。

単語の重みは1.0ですが、フレーズの重みは0.5です。これは、1つの文字列から欠落している単語全体にペナルティを課し、フレーズ全体が損なわれていないことを評価します。これらの文字列の多くは共通の1つの単語(危険)を持っているので、組み合わせ(領域と危険)が維持されるかどうかが本当に重要であるので、これは便利です。

最後に、最小の重みは10で、最大の重みは1で最適化されます。これは、2つのスコア(値句と値語)の最高があまり良くない場合、一致に大きなペナルティが課されることを意味しますが、 2つのスコアの最悪のものを大幅に罰することはありません。本質的に、これは、valueWordまたはvaluePhraseのいずれかが良いスコアを持つことを要求し両方ではないことを強調します。ある種の「私たちが得ることができるものを取りなさい」という考え方。

これらの5つの重みの最適化された値が、行われているファジー文字列マッチングの種類について何を言っているかは、本当に魅力的です。ファジー文字列マッチングの完全に異なる実際的なケースでは、これらのパラメーターは大きく異なります。これまでに3つの個別のアプリケーションに使用しました。

最終的な最適化では使用されていませんが、対角線上のすべての完璧な結果を得るために列をそれ自体に一致させるベンチマークシートが確立され、ユーザーがパラメーターを変更してスコアが0から逸脱する速度を制御し、検索フレーズ間の本質的な類似性を確認できます(理論的には、結果の偽陽性を相殺するために使用できます)

ファジー文字列マッチングベンチマーク

さらなるアプリケーション

このソリューションは、ユーザーがコンピュータシステムに、完全に一致しない文字列のセット内の文字列を識別させたい場所で使用できる可能性があります。(文字列の近似一致vlookupのように)。


したがって、これから取る必要があるのは、高レベルのヒューリスティック(一方のフレーズから他方のフレーズの単語を見つける、両方のフレーズの長さなど)と、レーベンシュタイン距離アルゴリズムの実装を組み合わせて使用​​することです。「最適な」一致を決定するのはヒューリスティック(ファジー)決定であるため、類似性を決定するには、思いついたメトリックの重みのセットを考え出す必要があります。

適切なヒューリスティックと重みのセットを使用すると、比較プログラムで、必要な決定をすばやく行うことができます。


13
ボーナス:加重されたヒューリスティックに追加のメトリックを含めたい場合(直線的に独立していないのは3つしか提供していないため)-wikipediaの
Alain

1
S2にたくさんの単語がある場合(そして、多くの小さなオブジェクトを作成しても、選択した言語で極端に遅くなるわけではありません)、トライを使用するとスピードアップできます。トライを使用した速くて簡単なレーベンシュタイン距離は、トライについての素晴らしい記事です。
JanX2、2012年

1
@Alainこれは興味深いアプローチです!私はあなたのアイデア(C ++で)を少し遊んでいますが、1つのポイント、の値を理解していませんvaluePhrase。私があなたのコードで正しく見るならば、それはレーベンシュタイン距離関数の戻り値です。「abcd efgh」検索テーブルのdouble / float値はどうしてですか?レーベンシュタイン距離は整数値であり、浮動小数点にするコード内でこれ以上の計算を行うことはできません。私は何を見逃していますか?
Andreas W. Wylach 2017年

1
@ AndreasW.Wylach素晴らしい観察。私が示したVBAはレーベンシュタイン距離を計算するだけでしたが、スプレッドシートで使用したヒューリスティック=valuePhrase(A2,B2)-0.8*ABS(LEN(B2)-LEN(A2))は次のとおりでした。したがって、レーベンシュタイン距離のペナルティを2つの「フレーズ」の長さの差の80%削減しました。このように、同じ長さの「フレーズ」は完全なペナルティを受けますが、「追加情報」(長い)を含むがそれ以外はほとんど同じ文字を共有する「フレーズ」はペナルティが少なくなります。
アラン

1
@Alain私の質問に戻ってくれてありがとう、ありがとう。あなたの説明は今物事をより明確にします。その間、私はvalue_phraseメソッドを実装しました。これは、フレーズのトークンを少し深く分析することです。つまり、フレーズトークンの順序/位置、非クエリトークンシーケンスであり、何かに関して少しあいまいさを受け入れます。 「abcd」と比較して「acbd」のように。phrase_valueスコアの傾向はあなたのものと同じですが、あちこちで少し低くなります。繰り返しになりますが、素晴らしいトレーニングで、あいまい検索アルゴリズムにインスピレーションを与えてくれました!
Andreas W. Wylach

88

この問題は、バイオインフォマティクスで常に発生します。上記の受け入れられた答え(ちなみに素晴らしかった)は、バイオインフォマティクスではNeedleman-Wunsch(2つの文字列を比較する)およびSmith-Waterman(長い文字列で近似の部分文字列を見つける)アルゴリズムとして知られています。彼らは素晴らしい働きをし、何十年もの間働き者でした。

しかし、比較する100万の文字列がある場合はどうでしょうか。これは1兆個のペアごとの比較であり、それぞれがO(n * m)です!現代のDNAシーケンサーは簡単に10億を生成します、それぞれ約200のDNA「文字」の長短います。通常、このような文字列ごとに、人間のゲノム(30億文字)に対して最適な一致を見つけたいと考えています。明らかに、Needleman-Wunschアルゴリズムとその親類はそうしません。

このいわゆる「アライメント問題」は活発な研究分野です。最も人気のあるアルゴリズムは、現在、10億の短い文字列と人間のゲノムの間の不正確な一致を数時間で妥当なハードウェア(8つのコアと32 GBのRAM)で見つけることができます。

これらのアルゴリズムのほとんどは、短い完全一致(シード)をすばやく見つけ、より遅いアルゴリズム(たとえば、Smith-Waterman)を使用してこれらを完全な文字列に拡張することで機能します。これが機能する理由は、私たちは本当に少数の近い一致にのみ興味があるため、共通点のない99.9 ...%のペアを取り除くことは報われるということです。

完全一致を見つけることは、不正確な一致を見つけるのにどのように役立ちますか?ええと、クエリとターゲットの間の単一の違いのみを許可するとします。この違いはクエリの右半分または左半分のいずれかで発生する必要があるため、残りの半分は正確に一致する必要があることが簡単にわかります。このアイデアは、複数の不一致に拡張することができ、ELANDの基礎となります、イルミナのDNAシーケンサーで一般的に使用されるアルゴリズムのます。

正確な文字列マッチングを行うための非常に優れたアルゴリズムが多数あります。長さが200のクエリ文字列と長さが30億のターゲット文字列(人間のゲノム)が与えられた場合、クエリの部分文字列と正確に一致する長さkの部分文字列があるターゲット内の任意の場所を見つけたいと思います。簡単なアプローチは、ターゲットのインデックス作成から始めることです。すべてのkロングの部分文字列を取り、配列に入れてソートします。次に、クエリの各k長のサブストリングを取得し、ソートされたインデックスを検索します。ソートと検索はO(log n)時間で実行できます。

しかし、ストレージが問題になる可能性があります。30億文字のターゲットのインデックスは、30億のポインターと30億のk長の単語を保持する必要があります。これを数十ギガバイト未満のRAMに収めるのは難しいようです。しかし驚くべきことに、Burrows-Wheeler変換を使用してインデックスを大幅に圧縮することができ、それでも効率的にクエリを実行できます。人間のゲノムのインデックスは、4 GB未満のRAMに収まります。このアイデアは、BowtieBWAなどの一般的なシーケンスアライナーの基礎です。

または、ポインタのみを格納し、ターゲット文字列内のすべてのサフィックスの同時インデックス(基本的に、kのすべての可能な値の同時インデックス)を表すサフィックス配列を使用できます。Burrows-Wheeler変換についても同様です。 )。32ビットポインターを使用する場合、人間のゲノムのサフィックスアレイインデックスには12 GBのRAMが必要です。

上記のリンクには、豊富な情報と主要な研究論文へのリンクが含まれています。ELANDリンクは、関連する概念を示す有用な図を含むPDFに移動し、挿入と削除の処理方法を示します。

最後に、これらのアルゴリズムは基本的に単一の人間のゲノム(10億の短い文字列)の(再)シーケンスの問題を解決しましたが、DNAシーケンステクノロジーはムーアの法則よりもさらに速く向上し、1兆文字のデータセットにすばやくアプローチしています。たとえば、現在、1億文字程度の長さの脊椎動物10,000種のゲノムをシーケンシングするプロジェクトが進行中です。当然のことながら、データに対してペアワイズに不正確な文字列マッチングを実行する必要があります...


3
本当に良い荒廃。いくつかの修正:インフィックスのソートには、O(log n)ではなく、少なくともO(n)が必要です。また、O(log n)検索は実際には遅すぎるため、通常、追加のテーブルを作成してO(1)ルックアップ(q-gramインデックス)を取得します。さらに、これをsuffix配列と異なる方法で処理する理由がわかりません。これは、後者の最適化であり、違います(実際には固定長以上のものは必要ないため、接尾辞ではなく固定長のinfixをソートします)。
Konrad Rudolph

1
さらに、これらのアルゴリズムは、de novoシーケンスにはまだ非現実的です。マッピングに使用できる参照配列がある限り、それらはヒトゲノムの配列決定を解決しました。しかし、de novoアセンブリには他のアルゴリズムが必要です(まあ、マッピングに基づいているアライナもありますが、コンティグをつなぎ合わせるのはまったく別の問題です)。最後に、恥知らずなプラグイン:私の学士論文には、ELANDアルゴリズムの詳細な説明が含まれています。
Konrad Rudolph、2012年

1
ありがとう。エラーを編集しました。固定長配列の説明から始めたのは、理解しやすいからです。サフィックス配列とBWTは少し理解しにくいですが、実際には、kの値が異なるインデックスを使用したい場合があります。たとえば、STARは接尾辞配列を使用して、スプライスされたアライメントを効率的に検索します。これはもちろん、RNAをゲノムに整列させるのに役立ちます。
Sten L

30

選択Bは、元の文字列から4文字(および2文字削除)しかないため、テスト文字列に近いと私は主張しています。一方、Cは茶色と赤の両方を含んでいるため、より近くに見えます。ただし、編集距離は長くなります。

と呼ばれるアルゴリズムがあります 2つの入力間の編集距離を測定するLevenshtein Distanceとます。

ここにがそのアルゴリズムのツールです。

  1. 距離Aとして選択Aを15と評価します。
  2. 選択肢Bを距離6として評価します。
  3. 選択肢Cを距離9として評価します。

編集:申し訳ありませんが、levenshteinツールで文字列を混ぜ続けます。正解に更新されました。


2
はい、そうですね。これを見てみましょう。個人的には、ターゲットにどれだけ近いかは気にしません。完璧を必要はありません。私は:)あなたの答えの結果を確認することができるまで)あなたのためのポイントません
Freesnöwを

18

Luaの実装、後世:

function levenshtein_distance(str1, str2)
    local len1, len2 = #str1, #str2
    local char1, char2, distance = {}, {}, {}
    str1:gsub('.', function (c) table.insert(char1, c) end)
    str2:gsub('.', function (c) table.insert(char2, c) end)
    for i = 0, len1 do distance[i] = {} end
    for i = 0, len1 do distance[i][0] = i end
    for i = 0, len2 do distance[0][i] = i end
    for i = 1, len1 do
        for j = 1, len2 do
            distance[i][j] = math.min(
                distance[i-1][j  ] + 1,
                distance[i  ][j-1] + 1,
                distance[i-1][j-1] + (char1[i] == char2[j] and 0 or 1)
                )
        end
    end
    return distance[len1][len2]
end

14

あなたはこのブログ投稿に興味があるかもしれません。

http://seatgeek.com/blog/dev/fuzzywuzzy-fuzzy-string-matching-in-python

Fuzzywuzzyは、文字列マッチングのためのレーベンシュタイン距離などの簡単な距離測定を提供するPythonライブラリです。これは、標準ライブラリのdifflibの上に構築されており、利用可能な場合はC実装のPython-levenshteinを利用します。

http://pypi.python.org/pypi/python-Levenshtein/


これを読んでいる他の人のために、Fuzzywuzzyは実際にAlainの素晴らしい投稿に多くのアイデアを実装しています。あなたが実際にそれらのアイデアのいくつかを使用することを探しているなら、それは始めるのに最適な場所です。
グレゴリーアレニウス2018

12

このライブラリは役に立ちます! http://code.google.com/p/google-diff-match-patch/

現在、Java、JavaScript、Dart、C ++、C#、Objective C、Lua、Pythonで利用できます

それもかなりうまくいきます。私はいくつかのLuaプロジェクトでそれを使用しています。

そして、他の言語に移植することはそれほど難しくないと思います!


2

検索エンジンまたはデータベースに対するフロントエンドのコンテキストでこれを行う場合は、Apache SolrなどのツールをComplexPhraseQueryParserプラグインとともに使用することを検討してください。この組み合わせにより、レーベンシュタイン距離によって決定される関連性でソートされた結果を持つ文字列のインデックスに対して検索できます。

着信クエリに1つ以上のタイプミスがある可能性がある場合、私たちはこれをアーティストと曲のタイトルの大規模なコレクションに対して使用しており、かなりうまく機能しています(コレクションが数百万の文字列にあることを考えると、驚くほど高速です)。

さらに、Solrを使用すると、JSONを介してオンデマンドでインデックスを検索できるため、見ている異なる言語間のソリューションを作り直す必要がありません。


1

これらの種類のアルゴリズムにとって非常に優れたリソースはSimmetricsです:http ://sourceforge.net/projects/simmetrics/

残念ながら、多くのドキュメントを含む素晴らしいウェブサイトはなくなりました:(再び戻ってきた場合、以前のアドレスはこれでした: http //www.dcs.shef.ac.uk/~sam/simmetrics.html

出来上がり(「ウェイバックマシン」提供):http : //web.archive.org/web/20081230184321/http : //www.dcs.shef.ac.uk/~sam/simmetrics.html

あなたはコードソースを研究することができます、これらの種類の比較のための数十のアルゴリズムがあり、それぞれ異なるトレードオフがあります。実装はJavaで行われます。


1

大量のテキストを効率的にクエリするには、編集距離/接頭辞編集距離の概念を使用できます。

距離の編集ED(x、y):項xから項yに取得する最小数の変換元

ただし、各用語とクエリテキスト間のEDの計算は、リソースと時間を消費します。したがって、最初に各用語のEDを計算する代わりに、Qgramインデックスと呼ばれる手法を使用して、一致する可能性のある用語を抽出できます。次に、それらの選択した用語にED計算を適用します。

Qgramインデックス技術の利点は、ファジー検索をサポートすることです。

QGramインデックスを適応させる1つの可能なアプローチは、Qgramを使用して逆インデックスを作成することです。そのQgramの下に、特定のQgramで構成されるすべての単語を格納します(完全な文字列を格納する代わりに、各文字列に一意のIDを使用できます)。これには、Javaでツリーマップデータ構造を使用できます。以下は用語の保存に関する小さな例です

col:col mbia、col ombo、gan col a、ta col ama

次に、クエリを実行するときに、クエリテキストと使用可能な用語の間の一般的なQgramの数を計算します。

Example: x = HILLARY, y = HILARI(query term)
Qgrams
$$HILLARY$$ -> $$H, $HI, HIL, ILL, LLA, LAR, ARY, RY$, Y$$
$$HILARI$$ -> $$H, $HI, HIL, ILA, LAR, ARI, RI$, I$$
number of q-grams in common = 4

共通のq-gramの数= 4。

一般的なQgramの数が多い用語については、クエリ用語に対するED / PEDを計算し、その用語をエンドユーザーに提案します。

この理論の実装は、次のプロジェクトで見つけることができます(「QGramIndex.java」を参照)。ご質問はお気軽にどうぞ。https://github.com/Bhashitha-Gamage/City_Search

Edit Distance、Prefix Edit Distance Qgram indexの詳細については、Hannah Bast教授の次のビデオをご覧くださいhttps://www.youtube.com/embed/6pUg2wmGJRo(レッスンは20:06から始まります


1

入力データが大きすぎる(たとえば、数百万の文字列)場合、この問題の実装は困難です。これを解決するために、弾性検索を使用しました。

クイックスタート:https : //www.elastic.co/guide/en/elasticsearch/client/net-api/6.x/elasticsearch-net.html

すべての入力データをDBに挿入するだけで、編集距離に基づいて任意の文字列をすばやく検索できます。これは、編集距離でソートされた結果のリストを提供するC#スニペットです(小さい順)。

var res = client.Search<ClassName>(s => s
    .Query(q => q
    .Match(m => m
        .Field(f => f.VariableName)
        .Query("SAMPLE QUERY")
        .Fuzziness(Fuzziness.EditDistance(5))
    )
));

どのライブラリを使用していますか?これが役立つためには、さらにいくつかの情報が必要です。
賭ける

0

ここでは、特定の単語間の距離を計算するためのgolang POCを設定できます。minDistanceおよびdifferenceその他のスコープを調整できます。

遊び場:https : //play.golang.org/p/NtrBzLdC3rE

package main

import (
    "errors"
    "fmt"
    "log"
    "math"
    "strings"
)

var data string = `THE RED COW JUMPED OVER THE GREEN CHICKEN-THE RED COW JUMPED OVER THE RED COW-THE RED FOX JUMPED OVER THE BROWN COW`

const minDistance float64 = 2
const difference float64 = 1

type word struct {
    data    string
    letters map[rune]int
}

type words struct {
    words []word
}

// Print prettify the data present in word
func (w word) Print() {
    var (
        lenght int
        c      int
        i      int
        key    rune
    )
    fmt.Printf("Data: %s\n", w.data)
    lenght = len(w.letters) - 1
    c = 0
    for key, i = range w.letters {
        fmt.Printf("%s:%d", string(key), i)
        if c != lenght {
            fmt.Printf(" | ")
        }
        c++
    }
    fmt.Printf("\n")
}

func (ws words) fuzzySearch(data string) ([]word, error) {
    var (
        w      word
        err    error
        founds []word
    )
    w, err = initWord(data)
    if err != nil {
        log.Printf("Errors: %s\n", err.Error())
        return nil, err
    }
    // Iterating all the words
    for i := range ws.words {
        letters := ws.words[i].letters
        //
        var similar float64 = 0
        // Iterating the letters of the input data
        for key := range w.letters {
            if val, ok := letters[key]; ok {
                if math.Abs(float64(val-w.letters[key])) <= minDistance {
                    similar += float64(val)
                }
            }
        }

        lenSimilarity := math.Abs(similar - float64(len(data)-strings.Count(data, " ")))
        log.Printf("Comparing %s with %s i've found %f similar letter, with weight %f", data, ws.words[i].data, similar, lenSimilarity)
        if lenSimilarity <= difference {
            founds = append(founds, ws.words[i])
        }
    }

    if len(founds) == 0 {
        return nil, errors.New("no similar found for data: " + data)
    }

    return founds, nil
}

func initWords(data []string) []word {
    var (
        err   error
        words []word
        word  word
    )
    for i := range data {
        word, err = initWord(data[i])
        if err != nil {
            log.Printf("Error in index [%d] for data: %s", i, data[i])
        } else {
            words = append(words, word)
        }
    }
    return words

}

func initWord(data string) (word, error) {
    var word word

    word.data = data
    word.letters = make(map[rune]int)
    for _, r := range data {
        if r != 32 { // avoid to save the whitespace
            word.letters[r]++
        }

    }
    return word, nil
}
func main() {
    var ws words
    words := initWords(strings.Split(data, "-"))
    for i := range words {
        words[i].Print()
    }
    ws.words = words

    solution, _ := ws.fuzzySearch("THE BROWN FOX JUMPED OVER THE RED COW")
    fmt.Println("Possible solutions: ", solution)

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