- あいうえお
- あいうえの
上記のような文字列の類似度を計算するやり方には「n-gram」や「Jaro–Winkler distance」など色々なやり方があるみたいです。一番簡単と思ったやり方は「n-gram」でした。少し古いですが livedoor の技術ブログに紹介記事があります。
実装が簡単なのもあって ruby のコードで github に公開されているリポジトリーがありました。
とりあえず、Usage に掲載されている以下例は、2つテキストの一致度 0.266... になるみたいです。github の ruby コードを動かしてみます。(3-gram の例になります)
- he is genius
- she is cute
0が完全不一致、1が完全一致です。類似度は0~1の間に正規化された数値として表現されるみたいです。
class Test def trigramify(text) trigs = [] text.chars.each_cons(3) { |v| trigs << v.join } trigs end end test = Test.new data1 = test.trigramify('he is genius') print(data1) print("\n") data2 = test.trigramify('she is cute') print(data2) print("\n\n") print((data1 | data2)) print(", count= #{(data1 | data2).size}") # 重複データを除いた合計数 print("\n") print((data1 & data2)) print(", count= #{(data1 & data2).size}") # 一致件数
["he ", "e i", " is", "is ", "s g", " ge", "gen", "eni", "niu", "ius"] ["she", "he ", "e i", " is", "is ", "s c", " cu", "cut", "ute"] ["he ", "e i", " is", "is ", "s g", " ge", "gen", "eni", "niu", "ius", "she", "s c", " cu", "cut", "ute"], count= 15 ["he ", "e i", " is", "is "], count= 4
単純にデータ件数を出すと、data1 = 10 個、data2 = 9 個、一致データ数が 4 件です。なんで、計算式は で求めているみたいです。
ポイントは、全データの合計数を出すときに、一致テキストを data1 と data2 の2個分にしないことですね。なんで、分母から一致データ数を引いてやります。
間違いというわけではないのですが、曖昧検索 (Fuzzy-matching) を N-gram 方式で行う の記事は、最終の計算結果が 0.6666... になっていますが、これは分母の数は data1 の総数(重複なし)になっていると思います。
C# で実装する
GitHub に公開しました。
参考にしたリポジトリーは 3-gram 固定になっていたので、1 ~ 5 に対応させてみました。
一般的には 1, 2, 3 までが基本となっているみたいですが、それ以上の例もあるみたいです。wiki を参照してみてください。