文字列の中に文字列の中の文字がすべて存在する?

ある文字列 a の中に別のある文字列 b を構成する文字がすべて存在するかを調べる。
もう少し条件を加えると、b の文字の順番を保っていること。例えば、こんな感じ。

# OK
a = 'xyz123'
b = 'xz3'
# NG
a = 'xyz123'
b = '3zx'    # 順番が合っていない
# NG
a = 'xyz123'
b = 'xzz3'   # 'z' が足りない。一度マッチした文字は使えない。

こういう機能が必要になった。

調査

で、2 通りの実装を思いついたのだが、どちらが速いかわからなかったので試してみた。
一つは正規表現を使う方法で、もう一つは String.index を使う方法。

コード
require 'benchmark'

search_word = 'aiueo'

loop     = 10000
subjects = Array.new(loop) {|i| i.to_s +  ('a'..'z').to_a.to_s * 2}

Benchmark.bm do |x|
    # 正規表現版
    # "aiueo" => /a.*i.*u.*e.*o/
    x.report do
        search_reg = Regexp.new(
                       search_word.split(//u).
                       map{|s| Regexp.escape(s, 'u').to_s }.
                       join('.*')
                     )
        subjects.each do |subject|
            index = search_reg =~ subject
        end
    end

    # index版
    x.report do
       subjects.each do |subject|
            index = nil
            head  = 0
            search_word.size.times do |i|
                index = subject[head, subject.size].index(search_word[i, 1])
                break unless index
                head += index + 1
            end
        end
    end
end
結果

上段が正規表現版、下段が String.index版。search_word を 4 パターンと、loopの数を 2 パターン試してみた。

# search_word = 'aiueo' => マッチする, loop = 10000
      user     system      total        real
  0.080000   0.010000   0.090000 (  0.088257)
  0.180000   0.020000   0.200000 (  0.188585)
# search_word = 'Xaiueo' => マッチしない, loop = 10000
      user     system      total        real
  0.010000   0.000000   0.010000 (  0.022401)
  0.050000   0.020000   0.070000 (  0.073230)
# search_word = 'aiuXeo' => マッチしない, loop = 10000
      user     system      total        real
  0.090000   0.000000   0.090000 (  0.097294)
  0.150000   0.030000   0.180000 (  0.170762)
# search_word = 'aiueoX' => マッチしない, loop = 10000
      user     system      total        real
  0.100000   0.020000   0.120000 (  0.116577)
  0.190000   0.040000   0.230000 (  0.236802)
# search_word = 'aiueo' => マッチする,  loop = 1
      user     system      total        real
  0.000000   0.000000   0.000000 (  0.000296)
  0.000000   0.000000   0.000000 (  0.000082)
  • 正規表現版の方が速かった。
  • loop の数が極端に少ないと 正規表現版の方が遅くなる。Regexpインスタンスを生成する時間がかかるのだろう。
    • 今回必要になった機能では、loop は 1000以上になるはずなので、これは気にしない。
  • subjects の要素は 27 〜 30 文字の文字列だけれど、これがもっと長くなると、正規表現の方が遅くなる。
    • 実際対象となる文字は 2 〜 20 文字くらいの文字なので、見なかったことにする。