読者です 読者をやめる 読者になる 読者になる

正規表現の後読みと先読みとAtomic grouping

R言語上級ハンドブックを使って勉強会をしてて、perl互換の正規表現というところで引っかかったのでメモ。
31ページから引用

# Perl互換の正規表現による柔軟な文字列抽出
text <- "@bar foo@example.com @foo@"
# 先読み・後読みなどの利用
strapply(text, "(?<!\\w)@(?>\\w+)(?!@)", backref = 0, perl = TRUE)

\が二重になってるのはRの仕様で文字列中の\をエスケープしないと関数に\として渡せないかららしい。

perl正規表現では(?~)は特別な意味を持っていて、そのうち先読みと後読み、それからAtomic groupingの3つがここで使われてる。
ちなみに上記のマッチの結果は@barになる。

先読みと後読み

マッチする条件を前後に拡張する。

パターン 意味
(?<=hoge) 前にhogeがあるときだけマッチ
(?<!hoge) 前にhogeがないときだけマッチ
(?=hoge) 後にhogeがあるときだけマッチ
(?!hoge) 後にhogeがないときだけマッチ

さっきの正規表現の最初と最後はそれぞれ

(?<!\\w)
(次に続く@の)前に\wがない
(?!@)
(前にある“@(?>\\w+)”の)後に@がない

という意味になる。

先読みと後読みを効果的に使う例として桁のカンマを挿入するcommafying numbersが有名で

my $text = "The population of 298444215 is growing";
text =~ s/(?<=\d)(?=(\d\d\d)+(?!\d))/,/g;
print "$text\n";
# The population of 298,444,215 is growing

とかできる。

atomic grouping

真ん中の正規表現(?>\\w+)はatomic groupingというやつで「強欲な」マッチングをする。
強欲の説明はコードをみたほうが話が早い。

my $text = "something";
if($text =~ /\w+ing/){
    print "MATCH\n";
}else{
    print "NOT match\n";
}
if($text =~ /(?>\w+)ing/){
    print "MATCH\n";
}else{
    print "NOT match\n";
}

これを実行すると

MATCH
NOT match

となる。
はじめの\w+は単純な正規表現ですべての半角英数字とアンダースコアが1つ以上続くという意味。そのあとにingが続いている。この場合マッチングではingの部分はパターン中のingに譲って\w+はsomethにマッチする
つぎの(?>\w+)は「強欲な」マッチングで後に続くingに譲らない。そのため(?>\w+)がsomething全体にマッチする。結局(?>\w+)ingはsomethingにマッチできない。

このテキストで、atomic groupingを使って結果が変わるわけでもないのにどうして採用したのか分からないけど、強欲なマッチングについて知らなかったのでタメにはなった。

参考にした本

詳説 正規表現 第3版

詳説 正規表現 第3版

R言語上級ハンドブック

R言語上級ハンドブック