web-dev-qa-db-ja.com

貪欲な対不本意な対有力な量指定子

私はこれを見つけました 優秀なチュートリアル 正規表現で、「貪欲」、「消極的」、「強迫」の量指定子が何をするかを直感的に理解している間、私の理解には重大な穴があるようです。

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

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.

説明では、eating入力文字列全体、文字はconsumed、matcherに言及していますバッキングオフ、「foo」の右端の出現はregurgitatedなど。

残念ながら、ニースの比metaにもかかわらず、私はまだ誰が何を食べているのか理解していません...(簡潔に)how正規表現エンジンの動作を説明する別のチュートリアルを知っていますか?

あるいは、誰かが次の段落を多少異なるフレージングで説明できるなら、それは大歓迎です:

最初の例では、貪欲な量指定子。*を使用して、0回以上「何でも」を検索し、その後に「f」「o」「o」の文字を続けます。量指定子は貪欲であるため、式の。*部分は最初に入力文字列全体を使用します。この時点で、最後の3文字( "f" "o" "o")がすでに消費されているため(誰によって -))。したがって、マッチャーは、「foo」の右端の出現が逆流されるまで、一度に1文字ずつ(から右から左?)後退します( これはどういう意味ですか?)、その時点で一致が成功し、検索が終了します。

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

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

324
Regex Rookie

試してみます。

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

リラクタントまたは「貪欲でない」数量詞は、最初はできるだけ一致しません。したがって、.*は最初は何にも一致せず、文字列全体が一致しません。次に、マッチャーはfの突き合わせを試みますが、ストリングの一致しない部分は「x」で始まるため、機能しません。そのため、マッチャーはバックトラックし、貪欲でない数量詞をもう1つ一致させます(「x」に一致し、「fooxxxxxxfoo」は一致しません)。次に、fとの一致を試みますが、成功し、正規表現のoと次のoも一致します。成功!

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

possessive量指定子は貪欲な量指定子に似ていますが、逆戻りしません。したがって、文字列全体に一致する.*で始まり、一致しないものは何も残されません。その後、正規表現のfと一致するものは何もありません。所有量限定子はバックトラックしないため、そこでは一致しません。

447
Anomie

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

Visual Image

38
SIslam

「規制する」または「後退する」という正確な用語を聞いたことはありません。これらに取って代わるフレーズは「バックトラック」ですが、「regurgitate」は「バックトラックがそれを捨てる前に暫定的に受け入れられていたコンテンツ」と同じくらい良いフレーズのようです。

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

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

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

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

これは、foo.*に一致するときに一時的に含まれていたことを意味します。その試行が失敗したため、正規表現エンジンは.*で1文字少ない文字を受け入れようとします。この例で.*の前に成功したマッチがあった場合、エンジンは.*マッチ(右から左、あなたが指摘したように、それは貪欲な修飾子であるため)、入力全体と一致することができなかった場合、一致したものを再評価することを強制される可能性がありますbefore架空の例の.*

一致が成功し、検索が終了することを示します。

ただし、2番目の例は消極的であるため、最初に(who?) "nothing"を消費することから始めます。なぜなら「foo」

最初の何も.?*によって消費されます。これは、正規表現の残りが一致することを可能にするもののうち、可能な限り短い量を消費します。

文字列の先頭に表示されず、強制的に飲み込みます(who swallows?)

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

最初の文字(「x」)。これは、0と4で最初の一致をトリガーします。テストハーネスは、入力文字列がなくなるまでプロセスを続行します。 4と13で別の一致を見つけます。

3番目の例では、数量詞が所有格であるため、一致が見つかりません。この場合、入力文字列全体が。* +、(how?)によって消費されます

.*+は可能な限り消費し、正規表現全体で一致が見つからなかった場合、はバックトラックして新しい一致を見つけません。所有フォームはバックトラッキングを実行しないため、おそらく.*+ではなく、文字クラスまたは同様の制限であるaccount: [[:digit:]]*+ phone: [[:digit:]]*+で多くの用途が見られることはないでしょう。

これにより、入力が一致しない場合に潜在的な一致をバックトラックしてはならないことを正規表現エンジンに伝えるため、正規表現の一致を大幅に高速化できます。 (一致するコードをすべて手動で記述する必要がある場合、これはputc(3)を使用して入力文字を「プッシュバック」しないことと似ています。最初の試行で記述する単純なコードと非常に似ています。プッシュバックの1文字よりもはるかに優れているため、すべてのバックをゼロに巻き戻して再試行できます。

しかし、潜在的なスピードアップよりも、これにより、一致する必要があるものに正確に一致する正規表現を作成することもできます。私は簡単な例を考え出すのに苦労しています:)が、所有対貪欲量指定子を使用して正規表現を書くと、異なる一致が得られる可能性があり、どちらかがより適切かもしれません。

式の最後の「foo」を満たすために残されたものは何も残しません。バックオフすることなく何かをすべて捕捉したい場合は、所有量限定詞を使用します(back off off?);それはアウトパフォームします

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

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

24
sarnold

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

それがインターネット上で最良の説明であるかどうかはわかりませんが、それは合理的によく書かれており、適切に詳細に説明されています。あなたはそれをチェックアウトしたいかもしれません。

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

このプロセスは再帰的に使用されます。文字列を特定の正規表現に一致させるために、エンジンは正規表現を複数のピースに分割し、各ピースに個別にアルゴリズムを適用します。

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

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

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

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

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

19
David Z

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

Greedy-貪欲な量指定子と正規表現全体に可能な限り一致します。一致するものがない場合、貪欲な量指定子をバックトラックします。

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

上記のRegexには2つの部分があります。
(私と
(ii) 'foo'

以下の各ステップでは、2つの部分を分析します。 「合格」または「不合格」に一致する場合の追加のコメントは、中括弧内で説明されています。

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

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

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

ステップ4:
(i)。* = xfooxxxxxx-PASS(貪欲な量指定子 '。*'のバックトラッキング)
(ii)foo = foo-PASS
MATCHを報告

結果:1試合
インデックス「0」からインデックス「13」で終わるテキスト「xfooxxxxxxfoo」を見つけました。

消極的-消極的量指定子と可能な限り一致せず、正規表現全体と一致します。一致するものがない場合は、消極的な量指定子に文字を追加します。

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

上記の正規表現には2つの部分があります。
(私) '。*?'そして
(ii) 'foo'

ステップ1:
。*? = ''(空白)-PASS(消極的な量指定子 '。*?'と可能な限り一致します。 ''を持つインデックス0は一致します。)
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-FAIL(セル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-FAIL(セル8,9,10-8と11の間のインデックス)
一致に失敗しました。

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

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

ステップ10:
。*? = ''(空白)-PASS(消極的な量指定子 '。*?'と可能な限り一致します。インデックス13は空白です。)
foo =一致する文字がありません-FAIL(インデックス13の後に一致するものはありません)
一致に失敗しました。

結果:2試合
インデックス0で始まりインデックス4で終わるテキスト「xfoo」が見つかりました。
インデックス「4」で始まりインデックス13で終わる「xxxxxxfoo」というテキストを見つけました。

所有-可能な限り所有量と一致し、正規表現全体と一致します。バックトラックしないでください

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

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

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

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

結果:0マッチ

12
raka

Greedy Quantificationは、反復中に文字列の残りのすべての未検証文字を使用したパターンマッチングを伴います。未検証の文字は、アクティブなシーケンスで始まります。一致が発生しないたびに、末尾の文字はquarantinedであり、チェックが再度実行されます。

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

キャラクターの流れは、アクティブなシーケンスから検疫への流れです。その結果、元のシーケンスの多くが可能な限り一致するようになります。

Reluctant Quantificationは、文字の流れが反対であることを除いて、貪欲な修飾とほとんど同じです-つまり、それらはquarantineアクティブシーケンスに流れます。結果の動作は、元のシーケンスが可能な限り一致に含まれないようにすることです。

Possessive Quantificationquarantineを持たず、すべての固定アクティブシーケンス

0

貪欲:「可能な限り長い文字列に一致する」

気が進まない:「可能な限り短い文字列に一致する」

所有:これは、(貪欲で消極的ではなく)正規表現全体に一致するものを見つけようとしないため、少し奇妙です。

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

0
Tilo Koerbs