StupidDog's blog

IT関連の手近な所で、疑問に思った事を調べた記録

「正規表現の"*"と"?"の動作の違いを確認する」|ただいまRubyの修行中

はじめに

正規表現の"*"と"?"の動作の違いを確認する。
ある本で*1タグ付きの文字列を、タグで分割するコードを見かけました。
章の内容とは全然関係無いのですが、そこで使用されていた処理から再確認したことです。

内容

開始タグ"<b>"と終了タグ"</b>"のように/を含めたものが終了タグのような場合に、正規表現が"<\/*b>"や"<\/?b>"でも同じ結果になるような気になります。
すぐ思いつくテストケースでは、タグの位置やタグの有無だと結果は同じです。

そこで、タグ自体が崩れたテストケースとして、終了タグの打ち間違いとして"<//b>"のような文字列を混ぜると違いが出てきます。

確認のためのコード
# encoding:utf-8
require "strscan"

def display_match(text, pattern)
  sc = StringScanner.new(text)

  puts %(*** text="#{text}", regular=#{pattern.to_s} ***)
  n = 0
  while sc.scan_until(pattern)
    n += 1
    puts "loop.#{n}, pos=#{sc.pos}, matchd=#{sc.matched}"
  end
end

text = "<p>This is a <b>pen<//b>. This is a <b>box</b></p>" 

display_match text, %r{</*b>}
display_match text, %r{</?b>}
実行結果
*** text="<p>This is a <b>pen<//b>. This is a <b>box</b></p>", regular=(?-mix:<\/*b>) ***
loop.1, pos=16, matchd=<b>
loop.2, pos=24, matchd=<//b>
loop.3, pos=39, matchd=<b>
loop.4, pos=46, matchd=</b>
*** text="<p>This is a <b>pen<//b>. This is a <b>box</b></p>", regular=(?-mix:<\/?b>) ***
loop.1, pos=16, matchd=<b>
loop.2, pos=39, matchd=<b>
loop.3, pos=46, matchd=</b>

"*"を使用した場合は、"<//b>"にもマッチしている。

正規表現の"*"と"?"の違い。
  • "*"は「直前の正規表現と0回以上の一致」
  • "?"は「直前の正規表現と0回または、1回の一致」

まとめ

"*"では、"/"が2回一致した結果となった。
知ってしまうと当然だけども実際にそうなるコードを考えると何かの時に気付きになるかな。
あまり頻繁に正規表現を使用していなかったので"?"の動作をよく考えていなかったけど、「0回または1回」は「有るか無いか」となるので使える場所は多そうです。

おまけ

今回のきっかけになったコードです(一部)。
# encoding:utf-8

def parse_inline_styles(text)
  segments = text.split( %r{(</?.*?>)} ).delete_if {|x| x.empty?}
  segments.size == 1 ? segments.first : segments
end

parse_inline_styles "<p>This is a <b>pen<//b>. This is a <b>box</b></p>"
実行結果
["<p>", "This is a ", "<b>", "pen", "<//b>", ". This is a ", "<b>", "box", "</b>", "</p>"]

text#splitで、区切りとして指定した正規表現で一致した文字列を、結果とすることができるのは新しい発見です。
が、崩れた終了タグに一致しるノД≦)

こんな感じで終了。

# encoding:utf-8

def parse_inline_styles(text)
  segments = text.split( %r{(</?[A-Za-z]+>)} ).delete_if {|x| x.empty?}
  segments.size == 1 ? segments.first : segments
end

p parse_inline_styles "<p>This is a <b>pen<//b>. This is a <b>box</b></p>"
実行結果
["<p>", "This is a ", "<b>", "pen<//b>. This is a ", "<b>", "box", "</b>", "</p>"]

*1:Rubyベストプラクティス