貪欲対消極的対所有的量指定子


357

私はこの正規表現に関する優れたチュートリアルを見つけました。「貪欲」、「しぶしぶ」、「所有的」の数量詞が何をするかを直感的に理解していますが、私の理解には深刻な穴があるようです。

具体的には、次の例では:

Enter your regex: .*foo  // greedy quantifier
Enter input string to search: xfooxxxxxxfoo
I found the text "xfooxxxxxxfoo" starting at index 0 and ending at index 13.

Enter your regex: .*?foo  // reluctant quantifier
Enter input string to search: xfooxxxxxxfoo
I found the text "xfoo" starting at index 0 and ending at index 4.
I found the text "xxxxxxfoo" starting at index 4 and ending at index 13.

Enter your regex: .*+foo // possessive quantifier
Enter input string to search: xfooxxxxxxfoo
No match found.

説明では、入力文字列全体を食べる、文字が消費される、マッチャーがバックオフする、右端の「foo」の出現が逆流れるなどについて言及しています。

残念ながら、いい比喩にもかかわらず、私はまだ誰が何を食べているのかわかりません... 正規表現エンジンがどのように機能するかを(簡潔に)説明する別のチュートリアルを知っていますか?

あるいは、誰かが次の段落をやや異なる言い回しで説明できれば、それは非常にありがたいです:

最初の例では、貪欲な量指定子。*を使用して「何でも」を0回以上検索し、その後に文字「f」「o」「o」を続けています。量指定子は貪欲であるため、式の。*部分は最初に入力文字列全体を消費します。この時点では、最後の3文字( "f" "o" "o")が既に(誰によって?)消費さているため、全体的な式は成功しません。したがって、マッチャーは、 "foo"の右端のオカレンスが逆流されるまで(これは何を意味するのか)、一度に1文字ずつゆっくりと(右から左へ?)バックオフします。

ただし、2番目の例は消極的であるため、最初に(誰が)「何も」を消費しないことから始まります。「foo」は文字列の先頭に表示されないため、最初の文字(「x」)を飲み込む(誰が飲み込むのか)ことが強制され、0と4で最初の一致がトリガーされます。テストハーネスはプロセスを続行します入力文字列がなくなるまで。4と13で別の一致を見つけます。

3番目の例は、量指定子が所有格であるため、一致を見つけることができません。この場合、入力文字列全体が。* +によって消費されます(どのように?)。バックオフせずにすべてを捕捉したい場合は、所有量指定子を使用します(バックオフとはどういう意味ですか?)。一致がすぐに見つからない場合は、同等の貪欲な量指定子よりも優れています。


22
最大数量が好き*+?している貪欲。 最小限の数量が好き*?+???している怠惰。 所有数量が好き*+++?+されている粘着性。
tchrist 2012年

6
この質問は、「量化子>違いの詳細...」のスタックオーバーフローの正規表現に関するFAQに追加されました。
aliteralmind 2014

興味のあるもの:Java™チュートリアル- 貪欲、消極的、所有的数量詞の違い -セクションを表示するには下にスクロールしてください。
Guy Coder

回答:


495

私が試してみます。

貪欲できるだけ数量詞最初の試合。したがって、.*は文字列全体と一致します。次に、マッチャーはf次のものと一致しようとしますが、文字が残っていません。したがって、「バックトラック」し、貪欲な量指定子と一致する文字を1つ減らします(文字列の末尾の「o」は一致しません)。これはまだf正規表現のと一致しません。そのため、ステップが1つ戻り、貪欲な量指定子がもう一度1文字少なく一致します(文字列の末尾の "oo" は一致しません)。これはまだf正規表現のと一致しません。そのため、もう1ステップ戻ります(文字列の末尾の「foo」は一致しません)。さて、マッチャーは最終的fに正規表現のと一致し、ooも一致しています。成功!

消極できるだけわずかまたは「非貪欲な」数量詞最初の試合。したがって、.*最初は何も一致せず、文字列全体が一致しません。次に、マッチャーはf次のものを照合しようとしますが、文字列の一致しない部分は「x」で始まるため、機能しません。したがって、マッチャーはバックトラックし、貪欲でない量指定子をもう1文字照合します(現在は「x」に一致し、「fooxxxxxxfoo」は一致しません)。次に、と一致しようとしますがf、これは成功し、正規表現oの次ともo一致します。成功!

あなたの例では、次に、同じプロセスに従って、文字列 "xxxxxxfoo"の残りの一致しない部分からプロセスを開始します。

所有数量詞はただ貪欲数量詞のようなものですが、それは後戻りしません。したがって.*、最初は文字列全体を照合し、何も一致しないものを残します。次にf、正規表現のと一致するものは何も残っていません。所有量指定子はバックトラックしないため、一致はそこで失敗します。


15
+1良い答え。私は追加するだけです:正規表現をマスターする(第3版)を
ridgerunner

所有部分で、後半に少し@Anomieしかし、私はあなたが意味を考えることがで始まりそう .*+(予告「+」)
RD

3
では、所有格指定子は正確には何をするのでしょうか?これと一致しない場合?(つまり、その後に文字を
含める

4
@relipse:バックトラッキングが役に立たないことがわかっている状況で使用しますが、おそらく.*+すべてに一致するわけではありません。たとえば、パターンがある場合[xyz]*foo、x、y、zを[xyz]*ビットで一致させてバックトラックすることで、次のfooビットを一致させることはできません。そのため、所有権を持たせることで速度を上げることができます。
Anomie 2016年

4
@moodboom、所有的な量指定子が単純な貪欲な量指定子によって生成されない一致を生成するケースはゼロです(数学的な事実)。貪欲な量指定子が一致するときに一致ない場合があります 他のすべてのケース(貪欲と所有格が同じ結果を生成する)の場合、所有格指定子はパフォーマンスを向上させます。
ワイルドカード2017

49

シーンを視覚化するのは私の練習用の出力です

ビジュアル画像


3
最後のケースである所有格はnパスではなく、文字列全体を一度に取得する必要があると思います。
modをうまく処理する2017

@phyzome大丈夫だと思う?
SIslam

1
視覚的な説明をありがとう:)
Lars Moelleken

EXPRESSION .*?foo()、should't [f] [o] [o]矩形は黄色可能5th pass
トニックス

1
@tonixはい!表現.*?fooとの一致部分は黄色に着色する必要があり.*+fooます。
SIslam

24

正確な「規制」や「後退」という言葉を聞いたことがありません。これらを置き換えるフレーズは「バックトラック」ですが、「regurgitate」は「バックトラックが再び捨てる前に暫定的に受け入れられていたコンテンツ」と同じくらい良いフレーズのようです。

ほとんどの正規表現エンジンについて理解するための重要なことは、それらがバックトラックしているということです。それらは、正規表現の内容全体を一致させようとしている間、潜在的に部分的な一致を一時的に受け入れます。最初の試みで正規表現を完全に一致させることができない場合、正規表現エンジンはその一致の1つでバックトラックします。これは、マッチングしようと*+?、交互、または{n,m}異なっ繰り返しをし、再試行してください。(そして、はい、このプロセスに長い時間かかります。)

最初の例では、貪欲な量指定子。*を使用して「何でも」を0回以上検索し、その後に文字「f」「o」「o」を続けています。量指定子は貪欲であるため、式の。*部分は最初に入力文字列全体を消費します。この時点では、最後の3文字( "f" "o" "o")が既に(誰によって?)消費さているため、全体的な式は成功しません。

最後の3つの文字、fo、およびoすでに初期によって消費された.*ルールの一部。ただし、正規表現の次の要素であるfは、入力文字列に何も残っていません。エンジンは最初のマッチでバックトラックを強制され.*、最後以外のすべてのキャラクターとのマッチングを試みます。(それは賢いかもしれません 3つのリテラル用語があるため、最後の3つを除いて、でバックトラックに、このレベルの実装の詳細は知りません。)

したがって、マッチャーは、「foo」の右端の出現が逆流されるまで、一度に1文字ずつゆっくりと(右から左へ?)後退します(これはどういう意味ですか?)、

これは、fooがマッチング時に一時的に含まれていたことを意味し.*ます。その試みが失敗したため、正規表現エンジンはで1文字少ない文字を受け入れようとし.*ます。この例ののに一致が成功した場合.*、エンジンはおそらく.*一致を短縮しようとします(これは、貪欲な修飾子であるため、指摘したように右から左へ)、一致することができなかった場合全体の入力、それからそれがに一致したものを再評価することを余儀なくされるかもしれません.*私の仮説的な例では。

一致が成功するとポイントが検索を終了します。

ただし、2番目の例は消極的であるため、最初に消費することから始まります(誰が?)「何も」をます。「foo」だから

最初は何も消費されません.?*。これにより、残りの正規表現を一致させることができるすべてのものが最短で消費されます。

文字列の先頭に表示されない、それは飲み込むことを強制されます(誰が飲み込むのですか?)

再び、.?*最初の文字をバックトラックした後、最初の文字を消費し、正規表現全体を可能な限り最短の一致と一致させません。(この場合、正規表現エンジンは消極的.*?であるため、一致を左から右に拡張してい.*?ます。)

最初の文字( "x")は、0と4で最初の一致をトリガーします。テストハーネスは、入力文字列がなくなるまで処理を続けます。4と13で別の一致を見つけます。

3番目の例は、量指定子が所有格であるため、一致を見つけることができません。この場合、入力文字列全体が。* +によって消費されます(どのように?

A .*+は可能な限り多くを消費し、正規表現全体が一致を見つけられない場合、新しい一致を見つけるためにバックトラックません。所有形式ではバックトラッキングが実行されないため、での多くの使用法は見られないでしょう。.*+むしろ、文字クラスまたは同様の制限で使用されますaccount: [[:digit:]]*+ phone: [[:digit:]]*+

入力が一致しない場合、一致する可能性があるものをバックトラックしないように正規表現エンジンに指示しているため、これにより正規表現の一致が大幅に高速化されます。(一致するすべてのコードを手動で記述する必要がある場合、これは決して使用しないのと同じですputc(3)入力文字を「プッシュバック」するです。最初の試行で書き込む可能性のある単純なコードと非常に似ています。正規表現エンジンを除いてプッシュバックの単一の文字よりも優れているため、すべてをゼロに巻き戻して再試行できます。

しかし、潜在的な高速化だけでなく、これにより、一致させる必要があるものと完全に一致する正規表現を作成することもできます。私は簡単な例を思い付くのに苦労しています:)しかし、所有的vs貪欲な量指定子を使用して正規表現を書くと、異なる一致が得られる可能性があり、どちらか一方がより適切な場合があります。

式の最後の "foo"を満たすために何も残されません。バックオフせずに何かをすべて捕捉したい状況では、所有量指定子を使用します(バックオフとはどういう意味ですか?)。それは優れています

このコンテキストでの「バックオフ」とは、「バックトラッキング」を意味します。仮の部分一致を破棄して、別の部分一致を試行しますが、成功する場合としない場合があります。

一致がすぐに見つからない場合の同等の貪欲な量指定子。


2
私は、所有的量指定子が貪欲な量指定子が一致しないものと一致するケースは決してないと思います。私は次のことがそれを証明していると思います:貪欲な量指定子は常に可能な限り一致し、一致が見つからない場合はバックトラックします。所有格指定子は可能な限り一致し、一致が見つからない場合は終了します。したがって、貪欲な量指定子は、所有的量指定子が一致しないものと一致する可能性がありますが、逆ではありません。どちらも同じシーケンスで「ツリー」を検索するため、所有的量指定子はより簡単に諦めます。;)
ワイルドカード2016年

2
確認済み:「これが、原子のグループ化と所有的な量指定子の目的です。バックトラックを許可しないことによる効率性。」 from regular-expressions.info したがって、この回答のステートメントは「潜在的な高速化だけでなく、一致させる必要のあるものと正確に一致する正規表現を作成することもできます。」実際にはかなり正確ではありません。
ワイルドカード2016年

1
@ワイルドカード、コメントありがとうございます。これが、例を思い付くのに苦労した理由を説明しているのかもしれません。へへ。
サルノルド2016年

19

http://swtch.com/~rsc/regexp/regexp1.html

これがインターネット上での最良の説明かどうかはわかりませんが、かなり適切に記述されており、適切に詳細に説明されているため、何度も繰り返し説明します。ぜひチェックしてみてください。

より高いレベル(詳細な説明はあまりありません)が必要な場合、見ているような単純な正規表現の場合、正規表現エンジンはバックトラックによって機能します。基本的に、文字列のセクションを選択( "食べる")し、正規表現をそのセクションと照合しようとします。それが一致すれば、すばらしい。そうでない場合、エンジンは文字列のセクションの選択を変更し、可能なすべての選択が試行されるまで、正規表現をそのセクションと照合します。

このプロセスは再帰的に使用されます。文字列を特定の正規表現と照合する際、エンジンは正規表現を断片に分割し、アルゴリズムを各断片に個別に適用します。

エンジンが文字列のどの部分と照合するかを選択するときに、貪欲、消極的、所有的数量詞の違い、および最初に機能しない場合にその選択を変更する方法が入ります。ルールは次のとおりです。

  • 貪欲な量指定子は、エンジンに文字列全体(または、少なくとも、正規表現の前の部分とまだ一致していないものすべて)から始めて、正規表現と一致するかどうかを確認するように指示します。もしそうなら、素晴らしいです。エンジンは残りの正規表現を続行できます。そうでない場合は、再試行しますが、チェックする文字列のセクションから1文字(最後の文字)をトリミングします。それが機能しない場合は、別の文字などが削除されます。したがって、貪欲な量指定子は、可能な一致を最長から最短の順にチェックします。

  • 消極的な量指定子は、文字列の可能な限り短い部分から開始するようにエンジンに指示します。一致する場合、エンジンは続行できます。一致しない場合は、チェックされている文字列のセクションに1文字を追加してそれを試します。一致が見つかるか、文字列全体が使い果たされるまで、以下同様に試行されます。したがって、消極的な量指定子は、可能な一致を最短から最長の順にチェックします。

  • 所有的量指定子は、最初の試行で貪欲な量指定子に似ています。これは、文字列全体をチェックすることから開始するようにエンジンに指示します。違いは、機能しない場合、所持量指定子は、一致がその場で失敗したことを報告することです。エンジンは見ている文字列のセクションを変更せず、それ以上の試行を行いません。

これが、所有格指定子の一致があなたの例で失敗する理由です:は、.*+一致する文字列全体に対してチェックされますが、その後、エンジンはその後、追加の文字を探しますfoo-もちろん、それらは見つかりません。 'すでに文字列の最後にあります。貪欲な量指定子である場合、バックトラック.*して、最後から2番目の文字、最後から3番目から最後の文字、最後から4番目から最後の文字までの唯一の一致を作成しようとします。そこfooの後に残され.*た文字列の前半「食べ」しています。


1
それは素晴らしい情報源です。ステートマシン図が大好きです。:)
Regex Rookie

@Regex Rookie:気に入ってくれてうれしいです:)そのサイトを見た後で、その目的が正規表現エンジンの代替実装を促進することであることを明確にすべきだと思います。私が(部分的に)追跡しているアルゴリズムと他の答えは、遅い方法です。これは、Webページで説明されているNFA / DFAのアイデアとは完全に別のアルゴリズムです。バックトラッキングは理解しやすいだけです。そのため、正規表現が一般に初心者に説明されます。
デビッドZ

@David Zaslavsky:良い説明。「貪欲な量指定子は、エンジンに文字列全体(または少なくとも、正規表現の前の部分とまだ一致していないものすべて)から開始するように指示します」の括弧内のコメントは重要です。それらは、消極的で所有的な量指定子にも適用されます。これにより、サンプルパターンを( "。* foo"; "。*?foo"; and "。* + foo")から( "foo。*"; "foo。*? ";および" foo。* + ")。
ジョンベントレー

実際、xfooxxxxxxfooは、正規表現の通常の(コンピュータサイエンスの意味)の。* fooに一致します。NFAは、任意の文字でループし、fooにジャンプできる状態になります。DFAはそのNFAの簡単な翻訳です。それは8つの状態で行うことができます。
user4951 2015年

@JimThioええ、所有格量ではないので。
David Z

13

これが、セルとインデックスの位置を使用した私の見解です(セルとインデックスを区別するには、この図を参照してください)。

貪欲-貪欲な数量詞と正規表現全体にできるだけ一致します。一致しない場合は、貪欲な量指定子に戻ります。

入力文字列: xfooxxxxxxfoo正規表現
。* foo

上記の正規表現には、
(i) '。*'と
(ii) 'foo'

の2つの部分があります。以下の各手順では、2つの部分を分析します。「合格」または「不合格」への一致に関する追加のコメントは、中括弧内に説明されています。

ステップ1:
(i)。* = xfooxxxxxxfoo-PASS( '。*'は貪欲な量指定子であり、入力文字列全体を使用します)
(ii)foo =インデックスの後に一致する文字が残っていません13-FAIL
一致が失敗しました。

ステップ2:
(i)。* = xfooxxxxxxfo-PASS(貪欲な量指定子 '。*'のバックトラック)
(ii)foo = o-FAIL
一致が失敗しました。

ステップ3:
(i)。* = xfooxxxxxxf-PASS(貪欲な量指定子 '。*'のバックトラック)
(ii)foo = oo-FAIL
一致が失敗しました。

ステップ4:
(i)。* = xfooxxxxxx-PASS(貪欲な数量詞 '。*'のバックトラック)
(ii)foo = foo-PASS
Report MATCH

結果:1件一致
インデックス0で始まりインデックス13で終わるテキスト "xfooxxxxxxfoo"が見つかりました。

消極的-消極的な量指定子にできるだけ一致せず、正規表現全体に一致します。一致しない場合は、消極的な量指定子に文字を追加します。

入力文字列: xfooxxxxxxfoo
。*?foo

上記の正規表現には2つの部分があります。
(i) '。*?' および
(ⅱ) 'foo'を

ステップ1:
。*?= ''(空白)-PASS(消極的な量指定子 '。*?'と可能な限り一致しない。 ''を持つインデックス0は一致です。)
-PASS( foo = xfo-FAIL(セル0,1,2-すなわち、 0および3)
一致が失敗した。

ステップ2:
。*?= x-PASS(不本意な量指定子 '。*?'に文字を追加します。'x 'を持つセル0が一致します。)
foo = foo-PASS
レポートMATCH

ステップ3:
。*?= ''(空白)-PASS(消極的な量指定子 '。*?'と可能な限り一致しない。 ''を持つインデックス4は一致する。)
foo = xxx-FAIL(セル4、5、6-つまり、 4および7)
一致しませんでした。

ステップ4:
。*?= x-PASS(消極的な量指定子 '。*?'に文字を追加します。セル4)
foo = xxx-FAIL(セル5、6、7-つまり5〜8のインデックス)
一致に失敗しました。

ステップ5:
。*?= xx-PASS(消極的な量指定子 '。*?'に文字を追加します。
セル4〜5 )foo = xxx-失敗(セル6、7、8-つまり6〜9のインデックス)
一致しませんでした。

ステップ6:
。*?= xxx-PASS(消極的な量指定子 '。*?'に文字を追加します。
セル4〜6 )foo = xxx-FAIL(セル7、8、9-つまり7〜10のインデックス)
一致しませんでした。

ステップ7:
。*?= xxxx-PASS(消極的な量指定子 '。*?'に文字を追加します。セル
4〜7 )foo = xxf-失敗(セル8,9,10-つまり、8〜11のインデックス)
一致に失敗しました。

ステップ8:
。*?= xxxxx-PASS(消極的な量指定子 '。*?'に文字を追加します。セル
4〜8 )foo = xfo-FAIL(セル9、10、11-つまり9〜12のインデックス)
一致しませんでした。

ステップ9:
。*?= xxxxxx-PASS(消極的な量指定子 '。*?'に文字を追加します。
セル4〜9 )foo = foo-PASS(セル10,11,12-つまり、10〜13のインデックス)
レポートMATCH

ステップ10:
。*?= ''(空白)-PASS(消極的な量指定子 '。*?'と可能な限り一致しない。インデックス13は空白。)
foo =一致する文字が残っていない-不合格(一致するインデックス13の後には何もない)
一致失敗した。

結果:2件一致
インデックス0で始まり
インデックス4で終わるテキスト「xfoo」を見つけました。インデックス4で始まりインデックス13で終わるテキスト「xxxxxxfoo」を見つけました。

Possessive-所有格の量子化器に可能な限り一致させ、正規表現全体に一致させます。バックトラックしないでください。

入力文字列: xfooxxxxxxfoo正規表現
。* + foo

上記の正規表現には、「。* +」と「foo」の2つの部分があります。

ステップ1:
。* + = xfooxxxxxxfoo-PASS(所有的量指定子 '。*'に可能な限り一致する)
foo =一致する文字が残っていない-FAIL(インデックス13の後に一致するものがない)
一致に失敗した。

注:バックトラッキングは許可されていません。

結果: 0件の一致


1

貪欲:「可能な限り長い文字シーケンスに一致する」

消極的:「可能な限り短い文字シーケンスに一致させる」

強迫観念:これは、(貪欲で消極的であるのとは対照的に)正規表現全体の一致を見つけようとしないため、少し奇妙です。

ちなみに、正規表現パターンマッチャーの実装では、バックトラックを使用しません。現実のパターンマッチャーはすべて非常に高速です。正規表現の複雑さにほとんど依存しません。


私の知る限り、現在、ほとんどの汎用使用実装は機能が満載されているため、バックトラッキングを使用できなくなりました。したがって、理論的には、場合によっては非常に(指数関数的に)遅くなるはずです。しかし、それらのほとんどの場合、パターンマッチャーに組み込まれた特別な最適化があります。
ロバート

0

貪欲な定量化には、反復中に文字列の残りの未検証文字をすべて使用したパターンマッチングが含まれます。検証されていない文字は、アクティブなシーケンスで始まります。マッチが発生しないたびに、最後のキャラクターが隔離されますれ、再度チェックが行われます。

正規表現パターンの先頭の条件のみがアクティブシーケンスによって満たされている場合、残りの条件を検疫に対して検証する試みが行われます。この検証が成功すると、検疫内の一致した文字が検証され、残りの一致しない文字は検証されないままとなり、プロセスが次の反復で新たに開始するときに使用されます。

文字の流れは、アクティブなシーケンスから検疫へと続きます。結果の動作は、元のシーケンスのできるだけ多くが一致に含まれることです。

Reluctant Quantificationは、文字の流れが反対であることを除いて、貪欲な修飾とほとんど同じです。つまり、文字が検疫から始まり、アクティブなシーケンスに流れ込みます。結果として生じる動作は、元のシーケンスのできるだけ少ないものが一致に含まれることです。

Possessive Quantificationに隔離がなく、固定されたアクティブシーケンスのすべてが含まれます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.