社内勉強会として、正規表現について喋ってみました。「最長一致」をテーマに、正規表現マッチのイメージが湧いていない人にもわかるような内容を目指したつもりですが、POSIXとPCREの最長一致の違いなど、ややマニアックな内容も含んでいます。
第29回PHP勉強会でも似た内容で発表させてもらいましたが、実はこの社内勉強会のプレゼンの一部を取り出して紹介したものでした。
今回のムービーはzoomeにアップロードしてみました。アップロードされている他のムービーと見比べると異色な内容のような気がしますが、ありがたく使わせて頂きます。
ムービー
発表資料
補足1: 「最短一致は使わない」について
基本的に最短一致は使うな、という結論にしてしまっているのですが、これは説明を端折っている面が大きいです。私の考えでは、正規表現初心者の頃は特にそうなのですが、「マッチングが最長一致であるために自分の意図通りのマッチングができなかった!最短一致こそ自分の求めていたものだ!」と思い込んでしまうことがあるような気がします。ところが、実際には最長一致のせいではなく、繰り返しを指示している表現が緩すぎる(例えば、本当は[^"]*と書くべきところを.*と書いてしまう、など)場合が殆どなのではないでしょうか。問題自体への理解不足を間違った方法で解決した気になったとしたら、潜在的なバグの原因になりかねません。最短一致を使うな、と呼びかける意図はそういった点です。正規表現エキスパートの人がわかっていて使う分には文句はありません。
以上、必死の言い訳でした。
補足2: longest matching と greedy matching の違いについて
プレゼン資料ではlongestとgreedyの違いを説明するのに「|」を使いましたけど、「|」以外でも違いは現れます。
簡単な実験をしてみましょう。「a+([ab]b)+」という正規表現で「aaabbb」をマッチさせることを考えてみます。
<?php $str = "aaabbb"; $regex = "(a+([ab]b)+)"; ereg($regex, $str, $match); printf("ereg: %s\n", $match[1]); preg_match("/$regex/", $str, $match); printf("preg_match: %s\n", $match[1]); mb_ereg($regex, $str, $match); printf("mb_ereg: %s\n", $match[1]); ?>
結果は次のようになります。
ereg: aaabbb preg_match: aaabb mb_ereg: aaabb
ereg(=POSIX)はlongestだけど、preg_matchとmb_eregはgreedyだ、というのが見て分かるかと思います。 greedyだとa+の繰り返しを欲張りすぎて3回繰り返してしまい、[ab]bが1回しか繰り返せないため、このような差が出るわけです。

コメント / トラックバック 2 件
分かりやすい資料をどうもありがとうございます。非常に参考になりました。
単なる参考情報ですが、PHP のマルチバイト正規表現でも、オプションを与えれば longest matching を行うことができます(PHP 4.3.0以降)。
mb_regex_set_options() で l を設定すればそのようになります。PHP マニュアルにはこの説明はありません。
結果は、以下になります。
mb_ereg_l: aaabbb
オプションの p は POSIX モードの意味なのですが、実際にはそうなっていないことになりますね。オプションを設定しない場合、 p はデフォルトで入っています。
mbstring のオーバーロード機能を使うと、ereg() が mb_ereg() に置き換わりますので、問題になる可能性はあります。
ありがとうございます。そんなオプション文字列があるなんて初めて知りました。
http://www.asahi-net.or.jp/~wv7y-kmr/memo/php_mbstring.html#mb_regex_option
先ほど上記ドキュメントを読ませて頂きました。鬼車が様々なマッチング方式に対応できるような柔軟性を持っていることは推測していましたが、こんな方法で切り替えられるというのは予想もしていませんでした。
“p”だけの指定だとlongest matchingではなく、greedy matchingのようだ、というのは私も確認しました。”b”や”d”も同様のようですね。
komuraさんは上記ドキュメントの筆者の方かと思いますが、有用なドキュメントを公開して頂いて本当にありがとうございます。
コメントする