QA@IT

正規表現で、行の最初を意味する ^ を指定しない場合、照合はどこから開始されますか?

3129 PV

今回は、自分のプログラムの質問というよりも、正規表現の仕様についての質問になります。

ただし、正規表現パターンと解析対象のテキストは、とりあえず、
http://qa.atmarkit.co.jp/q/2767
の質問と同じものを例として使います。

そこから一部分をそのまま抜き出したものが、

Regex regex = new Regex(@"(You have new mail)?.*Date: (\d\d)/(\d\d)");
Match match = regex.Match("<html><body>Welcome to our Meeting Room.<br>You have new mail<br>All rights reserved.<br>Date: 03/05</body></html>");

です。

私の疑問ですが、このパターン文字列の書き方だと、どこから正規表現のマッチング処理の照合を開始するのかがあいまいなのではないかと考えます。
たとえば、

Regex regex = new Regex(@"^.*(You have new mail)?.*Date: (\d\d)/(\d\d)");

のように、パターン文字列の最初に、行の最初を意味する ^ を指定してあれば、このパターン文字列の意味は明確になり、これでは HTML の中に You have new mail があっても判定できないことは理解できます。(最初の @"^.*" が最長一致してしまうので、その後ろにある @"(You have new mail)?" までマッチさせる必要がないので。)

しかし最初の質問と同じパターン文字列、

Regex regex = new Regex(@"(You have new mail)?.*Date: (\d\d)/(\d\d)");

の場合、HTML のいったいどこから @"(You have new mail)? の照合を開始するのかが明確になっていないと思います。

たとえば You have new mail ではなく <body> の存在を判定したい時は、

Regex regex = new Regex(@"(<body>)?.*Date: (\d\d)/(\d\d)");

と書きたくなりますがこれも、You have new mail の時と同様に判定できません。
しかし、<html> の存在を判定したい時は、

Regex regex = new Regex(@"(<html>)?.*Date: (\d\d)/(\d\d)");

と書くとこんどは判定できてしまいます。なぜかというとたまたま HTML の先頭に <html> があるからです。
@"(<body>)?"@"(<html>)?" も、最長一致を指定していることに変わりはありません。それなのに、たまたま HTML の先頭に <html> があるというだけで <html> は判定できて、<body> は判定できないという理屈が良く分かりません。

質問をクイズ形式に具体化します。
最初の質問と同じパターン文字列、

Regex regex = new Regex(@"(You have new mail)?.*Date: (\d\d)/(\d\d)");

を、パターン文字列の最初に、行の最初を意味する ^ を指定するように書き換えるとしたら、どう書き換えると等価なパターン文字列にできますか?

Regex regex = new Regex(@"^.*?(You have new mail)?.*Date: (\d\d)/(\d\d)");

かな?とも思ったのですが、違うようです。

疑問点が分かりにくいかもしれませんが、不明な点はお尋ねください。よろしくお願いいたします。

追記します。
上記の「照合」について補足します。私が言っている「照合」とは、たとえば、
http://msdn.microsoft.com/ja-jp/library/dsy130b4.aspx
の中で「結果として、正規表現エンジンは、次の表に示すような方法でこの正規表現パターンを入力文字列と照合します。」と説明されているような照合のことです。このページの例では needing a reed という文字列の「インデックス 0」から照合を開始しています。すなわちこのページの説明では、先頭から照合を開始する、と解釈できます。

しかし、そう解釈すると、私の例でたとえば、パターン文字列から ? を取り除いて、

Regex regex = new Regex(@"(You have new mail).*Date: (\d\d)/(\d\d)");

とした場合、これはマッチしないことになると思います?しかし実際には match.Success == true かつ match.Groups[1].Success == true になり、マッチできてしまいます。では、この場合は先頭から照合を開始していないのでしょうか?

  • 「照合」について補足を追記しました。 -
  • みなさまご回答ありがとうございました。 -

回答

Regex regex = new Regex(@"(You have new mail).*Date: (\d\d)/(\d\d)");

この例ですと、提示された URL の中では、「バックトラッキングを使用しない直線的な比較 」と「省略可能な量指定子または代替構成体によるバックトラッキング 」を読むとわかると思います。
照合は検索対象の文字列先頭から行いますが、パターンと一致しない場合は次の文字、次の文字と読み進めていますよね?
ですので、"You have new email" が現れた個所が一致候補となりそれ以降の文字列に対して、".*Date~" が見つかればこのパターンの一致という事になると思います。

編集 履歴 (0)
  • Microsoft のページをいろいろ読んでみたところ、パターン文字列の先頭に省略可能(1以上ではなく0以上の繰り返し)な量指定子があるかないかで、意味合いが違うのかなと思いました。パターン文字列の先頭に省略可能な量指定子がある場合は、入力文字列の先頭から照合を開始し、ない場合は、見つかるまでインデックスを進めるということを暗黙におこなうのだと思いました。 -
  • したがって、パターン文字列が @"(You have new mail)?.*Date: (\d\d)/(\d\d)" の場合は、先頭が省略可能な量指定子なので、入力文字列の先頭から照合を開始し、そこには "You have new mail" がないためいきなりマッチしないことが確定してしまうのだと思いました。 -
  • 一方、上記のパターン文字列から ? を抜いた @"(You have new mail).*Date: (\d\d)/(\d\d)" の場合は、先頭に省略可能な量指定子がないので、上記のような暗黙のインデックスの進めがおこなわれ、入力文字列の途中に "You have new mail" があればマッチするのだと思いました。 -
  • この2つのパターン文字列を見比べると、たんに ? の有無の違いに見えますが、? のないときにマッチするからと言って ? を足しても、上記のように暗黙のインデックスの進めの有無が異なるので、ダメだということですね。私のつまずきはそこにあったみたいです。 -
  • なお ^ を付ければ、その機能により、このような暗黙のインデックスの進めも明示的に抑制されるということになるのだろうと思います。 -

自分の質問に対し回答します。

書籍の紹介をします。
「照合の仕方 正規表現」をキーワードにして検索してヒットした、

詳説 正規表現 第3版 Jeffrey E.F. Friedl (著), 株式会社ロングテール (翻訳), 長尾 高弘 (翻訳)
http://www.amazon.co.jp/gp/product/4873113598

という書籍を Google ブックスでページを飛ばし飛ばし読んでみましたが、私には非常に分かりやすかったです。
150ページ目に「クイズの解答」というコラムがあり、そこには私の質問のパターン文字列と似た疑問に対する解説が載っていました。

編集 履歴 (0)

自分の質問に対し回答します。

最初の質問と同じパターン文字列、

Regex regex = new Regex(@"(You have new mail)?.*Date: (\d\d)/(\d\d)");を、パターン文字列の最初に、行の最初を意味する ^ を指定するように書き換えるとしたら、どう書き換えると等価なパターン文字列にできますか?

これは、先頭が省略可能な量指定子なので、たんに先頭に ^ を付けて、

Regex regex = new Regex(@"^(You have new mail)?.*Date: (\d\d)/(\d\d)");

としてもそれだけで等価だと思います。(改行など細かいことを考えないとして。)

ちなみに、出題が先頭が省略可能な量指定子ではない(? がない)場合だと、

Regex regex = new Regex(@"(You have new mail).*Date: (\d\d)/(\d\d)");

この先頭に ^ を付けてもこれと等価にするとしたら、前回の質問でshinsukeodaさんから教えていただいた記法を使い、

Regex regex = new Regex(@"^(?:(?!You have new mail).)*(You have new mail).*Date: (\d\d)/(\d\d)");

とすれば等価になるのかなと思います。

編集 履歴 (0)

正規表現のマッチングがどこから開始されるのか?
それは、マッチングさせる文字列の先頭からです。

^が何にマッチングするのかは、正規表現の実装ごとに異なることがあるので、それぞれの実装の仕様書やマニュアルを読んでください。

ある実装では単純にマッチング文字列の先頭にマッチしたり、またある実装では改行文字の直後にマッチしたり、またある実装では何にマッチさせるのかを選択できたりします。

編集 履歴 (0)

正規表現で、行の最初を意味する ^ を指定しない場合、照合はどこから開始されますか?

例に挙げたパターン (You have new mail)?.*Date: (\d\d)/(\d\d) の場合、"Date: " という文字列(最後の一文字は空白)があるところからです。

? はゼロ回または一回の繰り返しという意味、.* は任意の文字の繰り返しという意味なので、"Date: " 以前にどんな文字列があってもマッチするはずです。

そもそも ^.* というのは意味がなくて ^ の使い方が間違っていると思いますが。

正規表現については、以下のページが参考になると思います。「ASP.NET の」と書いてありますが、基本は C# の Windows アプリでも同じはずです。

ASP.NET の正規表現
http://msdn.microsoft.com/ja-jp/library/ms972966.aspx

How To: ASP.NET への入力を制約するために正規表現を使用する方法
http://msdn.microsoft.com/ja-jp/library/ms998267.aspx

編集 履歴 (0)
ウォッチ

この質問への回答やコメントをメールでお知らせします。