Perl Hackers Hub

第58回 正規表現の勘所―わかりづらい記法の覚え方,先読みや後読みの実践(3)

この記事を読むのに必要な時間:およそ 3 分

(1)こちら⁠2)こちらから。

先読みと後読みを理解する

正規表現を学んでいくと出会うのが,その位置の両側にどのような文字列があるかを指定する先読さきよ後読あとよです。まとめて先後読せんごよとも呼ばれます。

先後読みを使いこなすと,処理が簡潔になる恩恵を得られます。しかし,Perlの入門書で詳しく取り扱われていない場合が多く,いまいち理解できていない初学者は少なくないでしょう。筆者もその一人でした。

ここでは,筆者がどのように先後読みを理解して正規表現を読み書きしているのかを紹介します。

先後読みはゼロ幅マッチ

正規表現のメタ文字には文字自体にマッチするもののほか,文字にはマッチしないものの文字と文字の間などの位置にマッチするものがあります。後者の位置にマッチすること,またはその正規表現を言明げんめいassertion注1⁠,またはゼロ幅マッチzero-length matchと呼び,前者の幅のある文字や文字列とのマッチと区別します。

ゼロ幅マッチは,基本的な正規表現ですでに登場しています。たとえば,冒頭^末尾$単語境界\bはゼロ幅マッチです。そして重要なのが,先後読みもゼロ幅マッチであることです。

ゼロ幅マッチはマッチ文字列に入ることがないので,置換演算子s///で文字列置換の対象に入ることがありません。このことを指して,文字を消費しないとも言われます。

注1)
ゼロ幅であることを強調して,ゼロ幅の言明と呼ばれることもあります。

先後読みの記法

先読みと後読みにはそれぞれ肯定と否定があり,表3にある4種類のメタ文字が用意されています。

表3 先後読みのメタ文字

メタ文字名前説明
(?=●)肯定の先読みこの位置の右側が正規表現●にマッチすることを要請
(?!●)否定の先読みこの位置の右側が正規表現●にマッチしないことを要請
(?<=●)肯定の後読みこの位置の左側が正規表現●にマッチすることを要請
(?<!●)否定の後読みこの位置の左側が正規表現●にマッチしないことを要請

まず,簡単な肯定の先読みの例を見てみましょう。

my $intro = "私は牛乳と牛丼が好きです";
$intro =~ s/牛(?=丼)/豚/g;
print "$intro\n"; # => 私は牛乳と豚丼が好きです

正規表現牛(?=丼)は,の右側にがある場合,にマッチします。は先読み(?=丼)によってマッチに必要な条件ですが,ゼロ幅マッチなのでマッチ文字列には入りませんという文字を消費しません⁠⁠。そのため,マッチ文字列すなわち置換対象文字列はとなり,結果的に単語牛丼のみ豚丼に置換されます。

この例だとs/牛丼/豚丼/gでも代用できます。しかし,複数の正規表現演算子や文字列関数を駆使しないと実現できなさそうな要望も,先後読みを使うと単一の正規表現で簡潔に書ける場合があります。

先や後はマッチカーソルの動き

初学者が先後読みを学んだ際,⁠先や後の意味がわからない」と訴える場面をしばしば見かけます。

前項のs/牛(?=丼)/豚/gのサンプルは,の後にがある場合」といった説明もできるでしょう。先読みを使用したのに,その文字の後と取れる説明もできるわけです。ここでの「後」は,文字の登場順,つまり時間軸における「後」ととらえています。

しかし,先後読みの先や後は,時間軸のことではなくマッチ文字列を取り込んでいく先や後と位置関係でとらえるのがお勧めです。言い換えると,文章を読む目線の移動方向,すなわち書字方向の向かう先が先読み,その逆側が後読みです。日本語や英語を含む多くの言語の書字方向は左から右へ向かうので,右側を対象とするのが先読み,左側を対象とするのが後読みと考えて差し支えありません。

時間軸で考える場合も,をマッチ文字列候補に取り込んだけれど,次の文字をマッチ文字列候補として取り込む前に,先に次の文字を偵察するだと先読みの意味を正しく反映した説明となります。ただ,時間軸よりも位置関係でとらえるほうが誤解が起こりづらいと筆者は考えています。

「後」が時間軸を想像させるからか,⁠後読み」の代わりに「戻り読み」⁠後ろ読み」といった位置関係を強調した用語を使う人もいます注2⁠。また,先読みと後読みの英語での表現は,それぞれlook aheadとlook behindです注3⁠。戻り読みや後ろ読み,または英単語aheadやbehindがしっくり来る人は,そちらで覚えるのもよいでしょう。

注2)
それに対して,⁠先読み」「進み読み」⁠前読み」と呼ぶ人は,少なくとも筆者は見聞きしたことがありません。
注3)
perldoc perlreなどの公式ドキュメントでは,正規表現の用語としての先読みおよび後読みは,lookaheadおよびlookbehindと1つの単語になっています。

ゼロ幅マッチは一段下げて読む

先読みや後読みはゼロ幅マッチであると解説しましたが,ゼロ幅マッチを含んだ正規表現を読むときに筆者が頭の中で想像するのが,1行にした正規表現からゼロ幅マッチ部分だけを下の行に移す方法です。

図1では(1)の正規表現牛(?=丼)を例として,(2)で肯定の先読み(?=丼)を下の行に移しています。ゼロ幅マッチが位置にマッチすることと,先読みの方向がわかりやすいよう,(3)のように正規表現内の位置を線で示して,ゼロ幅マッチの指示を方向がわかりやすい図形にすると,視覚的にわかりやすくなります。

図1 ゼロ幅マッチの読み方(その1)

図1 ゼロ幅マッチの読み方(その1)

このように,通常の幅のある文字列マッチと,ゼロ幅マッチを上下に分けて書き,下の行にあるゼロ幅マッチの情報を上の行にある幅のある文字列マッチへの指示として書くことで,見通しが良くなります。

著者プロフィール

尾形鉄次(おがたてつじ)

大学院卒業後の2003年,Webメール開発会社に入社。以後10年以上,Perlを使ったWeb開発に携わる。

2015年,株式会社ガイアックスに入社。オンプレやクラウドの構築や保守運用を担当するインフラチームに所属しつつ,2018年頃から社外イベント運営やプログラミング教育などにも携わっている。

教育に関しては大学でのTAの経験などから課題感を持っていたが,2013年以降にPerl入学式などのコミュニティで教える側に立つことにつながり,そこで得られた知見を社内外で活かしている。

2019年よりJapan Perl Association理事,およびPerl入学式2代目代表を務める。