CodeKitHub
日本語
正規表現の「貪欲マッチング」と「遅延マッチング」:パターンが範囲を広く取りすぎてしまう理由

正規表現の「貪欲マッチング」と「遅延マッチング」:パターンが範囲を広く取りすぎてしまう理由

公開日 2026年7月24日

太字タグの内容を取得するには「<b>.*</b>」と記述し、「<b>Hello</b>」でテストしてみると、完璧に動作します。 次に、同じ行に2つの太字タグが含まれる実際のHTML — <b>Hello</b> and <b>World</b> — で実行すると、2つの一致が得られるはずが、両方のタグにまたがる1つの巨大な一致が得られてしまいます。 これは正規表現エンジンのバグではなく、*+、および{n,m}のデフォルトの挙動であり、何が起きているかさえ分かれば、たった1文字の修正で解決できます。

.* が予想以上に多くの文字をマッチさせる理由

デフォルトでは、正規表現の量指定子は 貪欲 です — .* は「いくつかの文字に一致する」という意味ではなく、「可能な限り多くの文字に一致させ、パターンの残りの部分が絶対に必要とする場合にのみ、一致範囲を縮小する」という意味です。

<b>.*</b><b>Hello</b> and <b>World</b> の処理を段階を追って見てみましょう:

  1. <b> は最初の <b> に一致します。
  2. .* は、文字列の残りの部分全体 — 2番目の <b>World</b> を含め、末尾までのすべて — を消費することから始まります。
  3. その後、エンジンは一致を完了させるために </b> を見つける必要があり、そのため .* の末尾から 1 文字ずつ遡って検索を開始します。
  4. (末尾から逆方向にスキャンした際)</b> が収まる最初の位置は、文字列の最後にある </b> であり、「論理的には」そこで停止すべき最初の位置ではありません。

したがって、マッチの結果は <b>Hello</b> and <b>World</b> 全体となります。これは、貪欲マッチングが常に可能な限り長い文字列を最初に試み、パターンの残りの部分が成立するために必要な最小限の範囲でしか文字列を短縮しないためです。

修正方法:? を使用して量指定子を遅延型にする

量指定子の直後に ? を追加すると、貪欲型から遅延型(「非貪欲型」または「消極型」とも呼ばれる)に切り替わります: *?+???{n,m}?

遅延型量指定子は逆の動作をします。つまり、最初は可能な限り少ない文字数のみを一致させ、パターンの残りの部分がまだ成立しない場合にのみ拡張を行います。

同じ文字列に対して <b>.*?</b> を適用すると:

  1. <b> が最初の <b> に一致します。
  2. .*? は、0文字に一致することから始まります。
  3. エンジンは、この位置で </b> が一致するかどうかを確認します。一致しません(現在は「Hello…」の位置にあり、まだ </b> の位置に到達していないため)。そこで、 .*? を正確に1文字分展開し、再度確認します。
  4. この処理は、.*?Helloを正確に消費するまで、1文字ずつ繰り返されます。その時点で、</b>が即座に一致します。

結果:<b>Hello</b><b>World</b> が 2 つの別々のマッチとして返されます。これは、タグや区切り文字のような構造を解析する際、ほとんどの場合、実際に望んでいた結果です。

実際に貪欲なマッチングが必要な場合(単なる「間違ったデフォルト」ではない)

貪欲なマッチングは言語上の間違いではありません。これは、別の、同様に一般的なケースにおいて正しい動作です。つまり、内部の最小単位ではなく、何かの最外側の境界に一致させる場合です。 JSON のようなテキストの塊から「最初の { と最後の } の間のすべて」を抽出する場合(たとえば、ネスト構造に関係なくオブジェクト全体を取得する場合など)、 greedy こそがまさに適切であり、lazy では代わりに最初の内部の } で停止してしまい、切り詰められた無効な断片が得られてしまいます。

経験則:区切り文字で囲まれた小さなチャンクが繰り返される場合は「lazy」(タグ、引用符で囲まれた文字列、リスト項目);「外側の範囲全体を取得する」場合は「greedy」

クイックリファレンス

パターン 動作 使用する場面
.*, .+, {n,m} グリーディ — 可能な限り長いものに一致 最外側のスパンを取得したい場合、または文字列内に一致が1つしかない場合
.*?, .+?, {n,m}? レイジー — 可能な限り短いものに一致 同様の区切り文字で区切られたチャンクが複数あり、それぞれを個別に取得したい場合

テスト文字列ではなく実際の入力データに対して、パターンがどの動作をしているか不明な場合は、 正規表現テスターで、リアルタイムのマッチハイライト機能を使って実行すると、その違いがすぐにわかります。グリーディな処理では1つの巨大なハイライトブロックが表示されるのに対し、レイジーな処理では複数の独立したブロックが表示されるため、文字単位で推論するよりも通常は迅速に判断できます。

← ブログに戻る