2007年05月01日 01:45 [Edit]

regexp - (?=lookahead) and (?<=lookbehind) assertions

つっこみの方はちょっと後回しにして...

odz buffer - 正規表現の否定 #2
とりあえず、dankogai さんあたりがツッコミを入れてくれると期待。

まずはこちらから。

Unknown::Programming - 文字列の先頭がある単語で始まらない正規表現って
qr/^(?:(?!foo).)+.*/
でいいのかなぁ・・・?

LookbehindLookahead
Positive(?<=expression)(?=expression)
Negative(?<!expression)(?!expression)

これらをまとめてLookaround Assertionと言いますが、これらは一体何で、そしてどんな時に使えばよいのでしょうか?なんだか難しそうですが、実はとても簡単です。まずは、以下の基本概念だけ覚えてしまいましょう。

RegexpにおけるAssertionは、その条件が合致するところまで検索カーソルを移動する

実は、これだけのことなのです。そして、その条件は、以下のとおりとなります。

Lookbehind

Lookahead
Positive
マッチする
(?<=expression)
マッチしたら、expressionの右にカーソルを移動
(?=expression)
マッチしたら、expressionの左にカーソルを移動
Negative
マッチしない
(?<!expression)
マッチしなかったら、expressionの右にカーソルを移動
(?!expression)
マッチしなかったら、expressionの左にカーソルを移動

重要なのは、Assertionは実際のマッチが行われる前に行われるということです。うまく使えば、それなしでは一度のs///eで出来なかった置換が出来るようになったり、不要なマッチが減って高速になったりしたりします。以下は、有名なカンマ化の例。

use Benchmark qw/timethese cmpthese/;

# Mastering Regular Expression 2nd Ed. pp 57-59

my $str = 'You owe me $1234567890 ever since.';
#         'You owe me $1,234,567,890 ever since.';

cmpthese(
    timethese(
        0,
        {
            'w/o assertion' => sub {
                local $_ = $str;
                1 while s/(\d)((\d\d\d)+\b)/$1,$2/g;
            },
            'with assertion' => sub {
                local $_ = $str;
                s/(?<=\d)(?=(\d\d\d)+(?!\d))/,/g;
              }
        }
    )
);
                  Rate  w/o assertion with assertion
w/o assertion  42440/s             --           -34%
with assertion 63826/s            50%             --

なぜこうなるか、なぜこうでないかは、フクロウ本こと"Mastering Regular Expression"をじっくり読むのが一番いいでしょう。ところでフクロウ本、私の手元にあるのは2nd Ed.ですが、3rd. Edがすでに出ているのですね。

それで、

qr/^(?:(?!foo).)+.*/

なのですが、「一応」の正解は、

qr/\A(?!foo)/;

でOKです。が、この場合はむしろわざとマッチさせてそれの否定をとった方が速いしわかりやすい。すなわち

if (/\A(?!foo)/){ 
  # ....
}

ではなく、

unless (/\Afoo/){ 
  # ....
}

の方がよい、ということです。こういった事例はフクロウ本にあちこち登場します。正規表現原理主義nに陥らないためにも、フクロウ本はお薦めです。

Dan the Regular Expressionist


この記事へのトラックバックURL

この記事へのトラックバック
詳説 正規表現 第3版 Jeffrey Friedl 株式会社ロングテール / 長尾高弘訳 [原著:Mastering Regular Expressions (3rd Ed.)] 正規表現の単語境界\bは便利ですが、これでうまく行かない場合もあります。 gist: 52e8422175f25d982fd9 - GitHub However when we ...
regexp - to \b or not to \b【404 Blog Not Found】at 2010年09月01日 18:36
この記事へのコメント
anonymousさん、
おや、ほんとだ。というわけでs/(?<=lookbehind)lookbehind/lookbehind/しました:-)
Dan the Typo Generator
Posted by at 2007年05月01日 03:46
一つ目の表、lookaheadがlookbehindになってますよ
Posted by anonymous at 2007年05月01日 03:29