LoginSignup
25
25

More than 5 years have passed since last update.

Ruby | did_you_mean gem でスペルミスを検出+ did_you_mean gem のコードリーディング

Posted at

Ruby | did_you_mean gem でスペルミスを検出+ did_you_mean gem のコードリーディング

概要

スペルミスを検出する gem、 did_you_mean が話題になっていたので早速試してみます。
また、実装が気になったのでコードリーディングもしてみます。

名前の類似度はレーベンシュタイン距離で算出している。
レーベンシュタイン距離 Wikipedia

文字数比でレーベンシュタイン距離が30%以上離れていなければ類似候補として表示する。
候補が複数ある場合は、複数表示。

インストール

$ gem install did_you_mean --no-ri --no-doc
$ gem list | grep did_you_mean
did_you_mean (0.7.0)

試行

require 'did_you_mean'

module Hogeable
  def hogeable
    "hogeable"
  end

  def hogec
    "hogec"
  end
end

class Hoge
  include Hogeable
  include Enumerable
  def hoge
    "hoge"
  end

  def hige
    "hige"
  end

  def hage
    "hage"
  end

  def bar1234567
  end
end
def call_did_you_mean_for_method(instance, m)
  instance.send(:"#{m}")
rescue => e
  puts e
end

def call_did_you_mean
  yield
rescue => e
  puts e
end

hoge = Hoge.new
call_did_you_mean_for_method(hoge, :hegeabl)
call_did_you_mean_for_method(hoge, :hogeabl)
call_did_you_mean_for_method(hoge, :hagec)
call_did_you_mean_for_method(hoge, :hege)
call_did_you_mean_for_method(hoge, :hogeabl)
call_did_you_mean { Hage.new }
call_did_you_mean { Arrai.new }
call_did_you_mean { Hogeabl.class }
  • 出力
$ ruby did_you_mean.rb
undefined method `hegeabl' for #<Hoge:0x000006002d8650>

    Did you mean? #hogeable

undefined method `hogeabl' for #<Hoge:0x000006002d8650>

    Did you mean? #hoge
                  #hogeable
                  #hogec

undefined method `hagec' for #<Hoge:0x000006002d8650>

    Did you mean? #hoge
                  #hige
                  #hage
                  #hogec

undefined method `hege' for #<Hoge:0x000006002d8650>

    Did you mean? #hoge
                  #hige
                  #hage
                  #hogec

undefined method `hogeabl' for #<Hoge:0x000006002d8650>

    Did you mean? #hoge
                  #hogeable
                  #hogec

uninitialized constant Hage

    Did you mean? Hash
                  Range
                  Hoge

uninitialized constant Arrai

    Did you mean? Array

uninitialized constant Hogeabl

    Did you mean? Hogeable
                  Hoge

コードリーディング

NameErrorの捕捉

interception gem を利用して例外をインターセプトしている。
did_you_mean gem / did_you_mean.rb
interception gem

レーベンシュタイン距離

文字数の類似度の判定はレーベンシュタイン距離を利用しているようです。
レーベンシュタイン距離 Wikipedia
did_you_mean gem / levenshtein.rb

マジでプルリクする5秒前

MP5(※ MK5)。踏みとどまりました。

その1

did_you_mean gem / word_collection.rb、14-18行目

イテレーターから外だしできそうな処理があったのでリファクタリングしてみた。
計測してみたけど、たいした効果がなかったのでプルリクエストはやめました。

  • before
def similar_to(target_word)
  select do |word|
    Levenshtein.distance(word.to_s, target_word.to_s) <= threshold(target_word)
  end
end
  • after
def similar_to(target_word)
  # to_s の呼び出しを メソッド数=>1回 に減らす
  target_word_str = target_word.to_s
  # threshold の呼び出しを メソッド数=>1回 に減らす
  thre = threshold(target_word)
  select do |word|
    Levenshtein.distance(word.to_s, target_word_str) <= thre
  end
end

その2

did_you_mean gem / levenshtein.rb 14-27行目

DidYouMean::Levenshtein.distance メソッド内のループ処理で、 each_with_index 関連のところを
簡潔にできるかな・・・と思ったのですが、あえて一時変数を利用している可能性もありそうなのでプルリクエストはやめました。

  • before
str1.each_char.each_with_index do |char1, i|
  e = i + 1
  str2.each_char.each_with_index do |char2, j|
    cost = (char1 == char2) ? 0 : 1
    x = min3(
      d[j+1] + 1, # insertion
      e + 1,      # deletion
      d[j] + cost # substitution
    )
    d[j] = e
    e = x
  end
  d[m] = x
end
  • after
str1.each_char.with_index(1) do |char1, i|
  str2.each_char.each_with_index do |char2, j|
    cost = (char1 == char2) ? 0 : 1
    x = min3(
      d[j+1] + 1, # insertion
      i + 1,      # deletion
      d[j] + cost # substitution
    )
    d[j] = i
    i = x
  end
  d[m] = x
end

参照

did_you_mean GitHub
interception GitHub
スペルミスでエラーが出たら、正しい名前を教えてくれる gem を作った
レーベンシュタイン距離 Wikipedia

25
25
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
25
25