先読みと後読みを理解する
正規表現を学んでいくと出会うのが、
先後読みを使いこなすと、
ここでは、
先後読みはゼロ幅マッチ
正規表現のメタ文字には文字自体にマッチするもののほか、
ゼロ幅マッチは、^
、$
、\b
はゼロ幅マッチです。そして重要なのが、
ゼロ幅マッチはマッチ文字列に入ることがないので、s///
で文字列置換の対象に入ることがありません。このことを指して、
先後読みの記法
先読みと後読みにはそれぞれ肯定と否定があり、
メタ文字 | 名前 | 説明 |
---|---|---|
(?=●) | 肯定の先読み | この位置の右側が正規表現●にマッチすることを要請 |
(?!●) | 否定の先読み | この位置の右側が正規表現●にマッチしないことを要請 |
(?<=●) | 肯定の後読み | この位置の左側が正規表現●にマッチすることを要請 |
(?<!●) | 否定の後読み | この位置の左側が正規表現●にマッチしないことを要請 |
まず、 正規表現 この例だと 初学者が先後読みを学んだ際、 前項の しかし、 時間軸で考える場合も、 「後」 先読みや後読みはゼロ幅マッチであると解説しましたが、 図1では このように、 ゼロ幅マッチは位置にマッチしますが、 上記で しかし、 幅のある1文字をキャプチャする例と比較すると、 先後読みを使うか、 前項で否定の先読みの例が登場しましたが、 紙幅の都合上、 筆者は東京在住なのですが、 検索に正規表現を使えるのであれば、 ちなみに、 「東京都」 本項では、 ゼロ幅マッチを一段下げて読む例で表すと、 本稿では、 Perlの正規表現は、 さて、my $intro = "私は牛乳と牛丼が好きです";
$intro =~ s/牛(?=丼)/豚/g;
print "$intro\n"; # => 私は牛乳と豚丼が好きです
牛(?=丼)
は、牛
の右側に丼
がある場合、牛
にマッチします。丼
は先読み(?=丼)
によってマッチに必要な条件ですが、丼
という文字を消費しません)。そのため、牛
となり、牛丼
のみ豚丼
に置換されます。s/牛丼/豚丼/g
でも代用できます。しかし、先や後はマッチカーソルの動き
s/牛(?=丼)/豚/g
のサンプルは、牛
の後に丼
がある場合」牛
をマッチ文字列候補に取り込んだけれど、ゼロ幅マッチは一段下げて読む
牛(?=丼)
を例として、(?=丼)
を下の行に移しています。ゼロ幅マッチが位置にマッチすることと、
隣接する複数のゼロ幅マッチはAND条件
my @data = ("1 Alice", "", "3 Carol", "Dave");
for my $line (@data) {
$line =~ s/^(?!\d)/WARNING:/; ――(1)
print "$line\n";
}
^
かつ否定の先読み(?!\d)
によって数値が右側に存在しない位置に文字列WARNING:
を挿入するサンプルです。正規表現がゼロ幅マッチのみで構成されており、1 Alice
WARNING:
3 Carol
WARNING:Dave
^(?!\d)
は位置に関する2個の条件のAND条件となっており、(?!\d)^
と書いても同じ意味です。しかしこの場合は、(?!\d)
の右側に書く意義はないでしょう。ただ、s///
に収めようとすると、$line =~ s{^(.)}{
my $x = $1; $x =~ /^\d$/ ? "$x" : "WARNING:$x";
}e;
$line
が空文字の場合に対応できていません。また、s{●}{■}e
の置換文字列を得るためのPerlコード■
の中にm//
があることが読みやすいかどうかは、否定の先後読みの実践
s/牛(?=丼)/豚/g
とs/牛丼/豚丼/g
の例で見たとおり、否定の先後読みを日本語の単語境界の代用として使う
\b
は日本語文字列に対応していない\b京都\b
ではうまくいきません否定の後読みで単語マッチを限定する
my @entries = (
"東京都の自宅を出て品川駅へ移動", ――(1)
"品川駅から東海道新幹線に乗車", ――(2)
"東海道新幹線の京都駅で下車",
"京都市右京区へ移動",
);
for my $entry (@entries) {
if ( $entry =~ /(?<!東)京都/ ) { ――(3)
print "$entry\n";
}
}
(?<!東)京都
によって、否定の先読みで単語マッチを限定する
my @entries = (
"東京都の自宅を出発",
"京都府の旅館に到着",
"京都嵐山を散策", ――(1)
"バスで京都市街を移動",
"京都文化博物館を観覧", ――(2)
);
for my $entry (@entries) {
if ( $entry =~ /(?<!東)京都(?!府)(?!市)/ ) { ――(3)
print "$entry\n";
}
}
(?<!東)京都(?!府)(?!市)
は、(?<!東)
のほか、(?!府)(?!市)
によって、(?!府)
と(?!市)
の関係も把握しやすくなります。
まとめ