「正規表現の"*"と"?"の動作の違いを確認する」|ただいま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>"にもマッチしている。
まとめ
"*"では、"/"が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>"]