Ich fand dieses hervorragende Tutorial zu regulären Ausdrücken und obwohl ich intuitiv verstehe, was "gierige", "widerstrebende" und "besitzergreifende" Quantifizierer tun, scheint es eine ernsthafte Lücke in meinem Verständnis zu geben.
Im folgenden Beispiel:
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.
In der Erklärung wird erwähnt , dass die gesamte Eingabezeichenfolge gegessen wurde , Buchstaben verbraucht wurden , der Matcher zurückgesetzt wurde , dass das Auftreten von "foo" ganz rechts wieder aufflammte usw.
Leider verstehe ich trotz der schönen Metaphern immer noch nicht, was von wem gegessen wird ... Kennen Sie ein anderes Tutorial, das (kurz) erklärt, wie Engines-Engines funktionieren?
Wenn jemand den folgenden Absatz in einer etwas anderen Formulierung erklären kann, wäre dies sehr dankbar:
Im ersten Beispiel wird der gierige Quantifizierer * verwendet, um "alles" null oder mehrmals zu finden, gefolgt von den Buchstaben "f" "o" "o". Da der Quantifizierer gierig ist, frisst der. * -Teil des Ausdrucks zuerst die gesamte Eingabezeichenfolge. Zu diesem Zeitpunkt kann der Gesamtausdruck nicht erfolgreich sein, da die letzten drei Buchstaben ("f" "o" "o") bereits verbraucht wurden ( von wem? ). Der Matcher zieht sich also langsam ( von rechts nach links? ) Buchstabe für Buchstabe zurück, bis das Auftreten von "foo" ganz rechts wieder aufgeflogen ist ( was bedeutet das? ). An diesem Punkt ist das Match erfolgreich und die Suche endet.
Das zweite Beispiel ist jedoch zurückhaltend, daher beginnt es damit, zuerst ( von wem? ) "Nichts" zu konsumieren . Da "foo" nicht am Anfang der Zeichenfolge erscheint, muss der erste Buchstabe (ein "x") geschluckt werden ( wer schluckt?), Was die erste Übereinstimmung bei 0 und 4 auslöst. Unser Testgeschirr setzt den Vorgang fort bis die Eingabezeichenfolge erschöpft ist. Es findet eine weitere Übereinstimmung bei 4 und 13.
Das dritte Beispiel findet keine Übereinstimmung, weil der Quantifizierer besitzergreifend ist. In diesem Fall wird die gesamte Eingabezeichenfolge von. * + ( Wie? ) Verbraucht, sodass nichts übrig bleibt, um das "foo" am Ende des Ausdrucks zu erfüllen. Verwenden Sie einen Possessivquantifizierer für Situationen, in denen Sie alles erfassen möchten, ohne sich jemals zurückzuziehen ( was bedeutet Zurücksetzen? ). In Fällen, in denen die Übereinstimmung nicht sofort gefunden wird, übertrifft sie den entsprechenden gierigen Quantifizierer.
quelle
*
,+
und?
sind gierig. Minimal quantifiers mögen*?
,+?
und??
sind faul. Possessive Quantoren mögen*+
,++
und?+
sind klebrig.Antworten:
Ich versuche es mal.
Ein gieriger Quantifizierer passt zuerst so gut wie möglich zusammen. Das
.*
passt also zur gesamten Saite. Dann versucht der Matcher,f
Folgendes zu finden, aber es sind keine Zeichen mehr vorhanden. Es wird also "zurückverfolgt", wodurch der gierige Quantifizierer mit einem Zeichen weniger übereinstimmt (wobei das "o" am Ende der Zeichenfolge nicht übereinstimmt). Das stimmt immer noch nicht mit demf
im regulären Ausdruck überein , daher wird ein weiterer Schritt zurückverfolgt, sodass der gierige Quantifizierer wieder mit einem Zeichen weniger übereinstimmt (wobei das "oo" am Ende der Zeichenfolge nicht übereinstimmt). Das stimmt immer noch nicht mit demf
im regulären Ausdruck überein , daher wird ein weiterer Schritt zurückverfolgt (wobei das "foo" am Ende der Zeichenfolge nicht übereinstimmt ). Jetzt stimmt der Matcher endlich mit demf
im regulären Ausdruck überein ,o
o
sind auch abgestimmt. Erfolg!Ein widerstrebender oder "nicht gieriger" Quantifizierer stimmt zunächst so wenig wie möglich überein. Das
.*
stimmt also zunächst nicht überein, so dass die gesamte Zeichenfolge nicht übereinstimmt. Dann versucht der Matcher,f
Folgendes zu finden, aber der nicht übereinstimmende Teil der Zeichenfolge beginnt mit "x", sodass dies nicht funktioniert. Der Matcher zieht sich also zurück, sodass der nicht gierige Quantifizierer mit einem weiteren Zeichen übereinstimmt (jetzt stimmt er mit dem "x" überein, sodass "fooxxxxxxfoo" nicht übereinstimmt). Dann versucht es, das zu findenf
, was erfolgreich ist, und daso
und das nächsteo
in der Regex-Übereinstimmung auch. Erfolg!In Ihrem Beispiel wird der Prozess dann mit dem verbleibenden nicht übereinstimmenden Teil der Zeichenfolge "xxxxxxfoo" neu gestartet, wobei derselbe Prozess ausgeführt wird.
Ein besitzergreifender Quantifizierer ist genau wie der gierige Quantifizierer, aber er zieht sich nicht zurück. Es beginnt also damit,
.*
die gesamte Zeichenfolge abzugleichen und nichts unübertroffen zu lassen. Dann ist nichts mehr übrig, was mit demf
in der Regex übereinstimmt . Da der Possessivquantifizierer nicht zurückverfolgt, schlägt die Übereinstimmung dort fehl.quelle
.*+
(beachte das "+").*+
allem. Wenn Sie beispielsweise ein Muster haben[xyz]*foo
, gibt es keine Möglichkeit, dass durch das Zurückverfolgen der mit dem[xyz]*
Bit übereinstimmenden x, y und z jemals das folgendefoo
Bit übereinstimmt, sodass Sie die Dinge beschleunigen können, indem Sie es besitzergreifend machen.Es ist nur meine Übungsausgabe, um die Szene zu visualisieren.
quelle
EXPRESSION .*?foo
Sollten in () die[f] [o] [o]
Rechtecke in nicht gelb sein5th pass
?.*?foo
und durchgeführt werden.*+foo
.Ich habe die genauen Begriffe "Aufstoßen" oder "Zurückziehen" noch nie gehört. Die Phrase, die diese ersetzen würde, ist "Backtracking", aber "Regurgitate" scheint eine ebenso gute Phrase zu sein wie jede andere für "den Inhalt, der vor dem Backtracking vorläufig akzeptiert wurde, warf ihn wieder weg".
Das Wichtigste, was bei den meisten Regex-Engines zu beachten ist, ist, dass sie zurückverfolgen : Sie akzeptieren vorläufig eine mögliche teilweise Übereinstimmung, während sie versuchen, den gesamten Inhalt des Regex abzugleichen. Wenn der Regex beim ersten Versuch nicht vollständig übereinstimmen kann, wird die Regex-Engine dies tun Rückzieher auf einem seiner Begegnungen. Er wird versuchen , passende
*
,+
,?
, Wechsel oder{n,m}
Wiederholung anders, und versuchen Sie es erneut. (Und ja, dieser Vorgang kann lange dauern.)Die letzten drei Buchstaben
f
,o
undo
wurden bereits von dem ursprünglichen verbrauchten.*
Teil der Regel. Das nächste Element in der Regex enthältf
jedoch nichts mehr in der Eingabezeichenfolge. Der Motor wird dazu gezwungen , Rückzieher auf seinem ursprünglichen.*
Spiel, und versucht , alle-but-die-letzten Zeichen übereinstimmen. (Es mag klug sein und auf alle bis auf die letzten drei zurückgehen, da es drei wörtliche Begriffe enthält, aber mir sind die Implementierungsdetails auf dieser Ebene nicht bekannt.)Dies bedeutet , das
foo
war vorläufig gewesen auch wenn Anpassung.*
. Da dieser Versuch fehlgeschlagen ist, versucht die Regex-Engine, ein Zeichen weniger zu akzeptieren.*
. Wenn es ein erfolgreiches Spiel war vor dem.*
in diesem Beispiel hätte, würde die Engine wahrscheinlich versuchen, das.*
Match zu verkürzen (von rechts nach links, wie Sie betont haben, weil es sich um ein gieriges Qualifikationsspiel handelt), und wenn es nicht passen könnte die gesamten Eingaben, dann könnte es gezwungen sein, neu zu bewerten, was es vor dem.*
in meinem hypothetischen Beispiel übereinstimmte .Das anfängliche Nichts wird von verbraucht
.?*
, wodurch die kürzest mögliche Menge von allem verbraucht wird , die es dem Rest des regulären Ausdrucks ermöglicht, übereinzustimmen.Wieder
.?*
verbraucht der das erste Zeichen, nachdem er den anfänglichen Fehler zurückverfolgt hat, den gesamten regulären Ausdruck nicht mit der kürzestmöglichen Übereinstimmung abzugleichen. (In diesem Fall erweitert die Regex-Engine die Übereinstimmung für.*?
von links nach rechts, da dies.*?
nur ungern erfolgt.)A
.*+
verbraucht so viel wie möglich und geht nicht zurück , um neue Übereinstimmungen zu finden, wenn der Regex als Ganzes keine Übereinstimmung findet. Da die Possessivform kein Backtracking durchführt, werden Sie wahrscheinlich nicht viele Verwendungen mit.*+
, sondern mit Zeichenklassen oder ähnlichen Einschränkungen sehen :account: [[:digit:]]*+ phone: [[:digit:]]*+
.Dies kann den Regex-Abgleich drastisch beschleunigen, da Sie der Regex-Engine mitteilen, dass potenzielle Übereinstimmungen niemals zurückverfolgt werden sollen, wenn eine Eingabe nicht übereinstimmt. (Wenn Sie den gesamten passenden Code von Hand schreiben müssten, wäre dies ähnlich wie wenn Sie ihn niemals verwenden würden
putc(3)
ein Eingabezeichen zurückschieben. Es wäre dem naiven Code sehr ähnlich, den man beim ersten Versuch schreiben könnte. Außer Regex-Engines viel besser als ein einzelnes Push-Back-Zeichen, können sie den gesamten Back auf Null zurückspulen und es erneut versuchen. :)Dies ist jedoch mehr als nur eine mögliche Beschleunigung. Sie können damit auch Regexs schreiben, die genau Ihren Anforderungen entsprechen. Ich habe Probleme, ein einfaches Beispiel zu finden :) Aber das Schreiben eines regulären Ausdrucks mit besitzergreifenden und gierigen Quantifizierern kann zu unterschiedlichen Übereinstimmungen führen, und der eine oder andere ist möglicherweise besser geeignet.
"Zurücksetzen" bedeutet in diesem Zusammenhang "Zurückverfolgen" - ein vorläufiges Teilmatch wegwerfen, um ein anderes Teilmatch zu versuchen, das möglicherweise erfolgreich ist oder nicht.
quelle
http://swtch.com/~rsc/regexp/regexp1.html
Ich bin mir nicht sicher, ob dies die beste Erklärung im Internet ist, aber sie ist einigermaßen gut geschrieben und angemessen detailliert, und ich komme immer wieder darauf zurück. Vielleicht möchten Sie es überprüfen.
Wenn Sie eine höhere Ebene (weniger detaillierte Erklärung) für einfache reguläre Ausdrücke wie den von Ihnen betrachteten wünschen, funktioniert eine Engine für reguläre Ausdrücke durch Zurückverfolgen. Im Wesentlichen wählt es einen Abschnitt der Zeichenfolge aus ("isst") und versucht, den regulären Ausdruck mit diesem Abschnitt abzugleichen. Wenn es passt, großartig. Wenn nicht, ändert die Engine die Auswahl des Abschnitts der Zeichenfolge und versucht, den regulären Ausdruck mit diesem Abschnitt abzugleichen, usw., bis alle möglichen Auswahlmöglichkeiten ausprobiert sind.
Dieser Prozess wird rekursiv verwendet: Bei dem Versuch, eine Zeichenfolge mit einem bestimmten regulären Ausdruck abzugleichen, teilt die Engine den regulären Ausdruck in Teile auf und wendet den Algorithmus auf jedes Teil einzeln an.
Der Unterschied zwischen gierigen, widerstrebenden und besitzergreifenden Quantifizierern tritt ein, wenn die Engine auswählt, mit welchem Teil der Zeichenfolge sie übereinstimmen möchten, und wie diese Auswahl geändert werden kann, wenn sie beim ersten Mal nicht funktioniert. Die Regeln lauten wie folgt:
Ein gieriger Quantifizierer weist die Engine an, mit der gesamten Zeichenfolge (oder zumindest mit der gesamten Zeichenfolge, die noch nicht mit vorherigen Teilen des regulären Ausdrucks übereinstimmt) zu beginnen und zu überprüfen, ob sie mit dem regulären Ausdruck übereinstimmt. Wenn ja, großartig; Der Motor kann mit dem Rest des regulären Ausdrucks fortfahren. Wenn nicht, wird es erneut versucht, aber ein Zeichen (das letzte) wird aus dem Abschnitt der zu überprüfenden Zeichenfolge entfernt. Wenn das nicht funktioniert, schneidet es ein anderes Zeichen usw. ab. Ein gieriger Quantifizierer überprüft also mögliche Übereinstimmungen in der Reihenfolge vom längsten zum kürzesten.
Ein widerstrebender Quantifizierer weist die Engine an, mit dem kürzestmöglichen Teil der Zeichenfolge zu starten. Wenn es übereinstimmt, kann der Motor fortgesetzt werden. Wenn nicht, fügt es dem Abschnitt der zu überprüfenden Zeichenfolge ein Zeichen hinzu und versucht dies usw., bis eine Übereinstimmung gefunden wird oder die gesamte Zeichenfolge aufgebraucht ist. Ein widerstrebender Quantifizierer überprüft also mögliche Übereinstimmungen in der Reihenfolge vom kürzesten zum längsten.
Ein Possessiv-Quantifizierer ist wie ein gieriger Quantifizierer beim ersten Versuch: Er weist die Engine an, zunächst die gesamte Zeichenfolge zu überprüfen. Der Unterschied besteht darin, dass der Possessivquantifizierer, wenn es nicht funktioniert, meldet, dass die Übereinstimmung sofort fehlgeschlagen ist. Die Engine ändert den Abschnitt der betrachteten Zeichenfolge nicht und unternimmt keine weiteren Versuche.
Aus diesem Grund schlägt die Possessiv-Quantifizierer-Übereinstimmung in Ihrem Beispiel fehl: Sie
.*+
wird mit der gesamten Zeichenfolge verglichen, mit der sie übereinstimmt, aber danach sucht die Engine nach zusätzlichen Zeichenfoo
- aber natürlich findet sie diese nicht, weil Sie Ich bin schon am Ende der Zeichenfolge. Wenn es ein gieriger Quantifizierer wäre, würde er zurückverfolgen und versuchen, die.*
einzige Übereinstimmung bis zum vorletzten Zeichen, dann bis zum drittletzten Zeichen und dann bis zum viertletzten Zeichen herzustellen, was erfolgreich ist, weil nur dann dortfoo
links, nachdem der.*
den früheren Teil der Saite "gegessen" hat.quelle
Hier ist meine Einstellung unter Verwendung der Zellen- und Indexpositionen (siehe das Diagramm hier , um eine Zelle von einem Index zu unterscheiden).
Gierig - Passen Sie so viel wie möglich an den gierigen Quantifizierer und den gesamten regulären Ausdruck an. Wenn es keine Übereinstimmung gibt, ziehen Sie den gierigen Quantifizierer zurück.
Eingabezeichenfolge : xfooxxxxxxfoo
Regex : . * Foo
Der obige Regex besteht aus zwei Teilen:
(i) '. *' Und
(ii) 'foo'.
Jeder der folgenden Schritte analysiert die beiden Teile. Zusätzliche Kommentare für eine Übereinstimmung mit "Bestanden" oder "Nicht bestanden" werden in geschweiften Klammern erläutert.
Schritt 1:
(i). * = Xfooxxxxxxfoo - PASS ('. *' Ist ein gieriger Quantifizierer und verwendet die gesamte Eingabezeichenfolge)
(ii) foo = Nach Index 13 - FAIL
Match fehlgeschlagen, kein übereinstimmendes Zeichen mehr .
Schritt 2:
(i). * = Xfooxxxxxxfo - PASS (Backtracking auf dem gierigen Quantifizierer '. *')
(Ii) foo = o - FAIL
Match fehlgeschlagen.
Schritt 3:
(i). * = Xfooxxxxxxf - PASS (Backtracking auf dem gierigen Quantifizierer '. *')
(Ii) foo = oo - FAIL
Match fehlgeschlagen.
Schritt 4:
(i). * = Xfooxxxxxx - PASS (Backtracking auf dem gierigen Quantifizierer '. *')
(Ii) foo = foo - PASS
Report MATCH
Ergebnis: 1 Übereinstimmung (en)
Ich habe den Text "xfooxxxxxxfoo" gefunden, der bei Index 0 beginnt und bei Index 13 endet.
Widerstrebend - Passen Sie so wenig wie möglich an den zögernden Quantifizierer an und passen Sie den gesamten regulären Ausdruck an. Wenn keine Übereinstimmung vorliegt, fügen Sie dem widerstrebenden Quantifizierer Zeichen hinzu.
Eingabezeichenfolge : xfooxxxxxxfoo
Regex: . *? Foo
Der obige reguläre Ausdruck besteht aus zwei Teilen:
(i) '. *?' und
(ii) "foo"
Schritt 1 :
. *? = '' (leer) - PASS (Passen Sie so wenig wie möglich an den widerstrebenden Quantifizierer an. *? '. Index 0 mit' 'ist eine Übereinstimmung.)
foo = xfo - FAIL (Zelle 0,1,2 - dh Index zwischen 0 und 3)
Übereinstimmung fehlgeschlagen.
Schritt 2 :
. *? = x - PASS (Fügen Sie dem widerstrebenden Quantifizierer '. *?' Zeichen hinzu. Zelle 0 mit 'x' ist eine Übereinstimmung.)
foo = foo - PASS
Report MATCH
Schritt 3 :
. *? = '' (leer) - PASS (Passen Sie so wenig wie möglich an den widerstrebenden Quantifizierer an '. *?'. Index 4 mit '' ist eine Übereinstimmung.)
foo = xxx - FAIL (Zelle 4,5,6 - dh Index zwischen 4 und 7)
Übereinstimmung fehlgeschlagen.
Schritt 4 :
. *? = x - PASS (Fügen Sie dem widerstrebenden Quantifizierer '. *?' Zeichen hinzu. Zelle 4.)
foo = xxx - FAIL (Zelle 5,6,7 - dh Index zwischen 5 und 8)
Übereinstimmung fehlgeschlagen.
Schritt 5 :
. *? = xx - PASS (Fügen Sie dem widerstrebenden Quantifizierer '. *?' Zeichen hinzu. Zelle 4 bis 5.)
foo = xxx - FAIL (Zelle 6,7,8 - dh Index zwischen 6 und 9) Übereinstimmung
fehlgeschlagen.
Schritt 6 :
. *? = xxx - PASS (Fügen Sie dem widerstrebenden Quantifizierer '. *?' Zeichen hinzu. Zelle 4 bis 6.)
foo = xxx - FAIL (Zelle 7,8,9 - dh Index zwischen 7 und 10) Übereinstimmung
fehlgeschlagen.
Schritt 7:
. *? = xxxx - PASS (Fügen Sie dem widerstrebenden Quantifizierer '. *?' Zeichen hinzu. Zelle 4 bis 7.)
foo = xxf - FAIL (Zelle 8,9,10 - dh Index zwischen 8 und 11)
fehlgeschlagen.
Schritt 8 :
. *? = xxxxx - PASS (Fügen Sie dem widerstrebenden Quantifizierer '. *?' Zeichen hinzu. Zelle 4 bis 8.)
foo = xfo - FAIL (Zelle 9,10,11 - dh Index zwischen 9 und 12)
fehlgeschlagen.
Schritt 9 :
. *? = xxxxxx - PASS (Fügen Sie dem widerstrebenden Quantifizierer '. *?' Zeichen hinzu. Zelle 4 bis 9.)
foo = foo - PASS (Zelle 10,11,12 - dh Index zwischen 10 und 13)
Report MATCH
Schritt 10 :
. *? = '' (leer) - PASS (Passen Sie so wenig wie möglich an den widerstrebenden Quantifizierer an '. *?'. Index 13 ist leer.
)
Spiel gescheitert.
Ergebnis: 2 Übereinstimmungen
Ich habe den Text "xfoo" gefunden, der bei Index 0
beginnt und bei Index 4 endet. Ich habe den Text "xxxxxxfoo" gefunden, der bei Index 4 beginnt und bei Index 13 endet.
Possessiv - Passen Sie so viel wie möglich an den Possessivquantifer an und passen Sie den gesamten regulären Ausdruck an. NICHT zurückverfolgen.
Eingabezeichenfolge : xfooxxxxxxfoo
Regex: . * + Foo
Die obige Regex besteht aus zwei Teilen: '. * +' Und 'foo'.
Schritt 1 :
. * + = Xfooxxxxxxfoo - PASS (Passen Sie so viel wie möglich an den Possessivquantifizierer '. *' An)
foo = Kein passendes Zeichen mehr - FAIL (Keine Übereinstimmung nach Index 13)
fehlgeschlagen.
Hinweis: Backtracking ist nicht zulässig.
Ergebnis: 0 Übereinstimmung (en)
quelle
Gierig: "Entspricht der längstmöglichen Zeichenfolge"
Widerstrebend: "Entspricht der kürzestmöglichen Zeichenfolge"
Possessiv: Dies ist ein bisschen seltsam, da es NICHT (im Gegensatz zu gierig und widerstrebend) versucht, eine Übereinstimmung für den gesamten regulären Ausdruck zu finden.
Übrigens: Keine Regex Pattern Matcher-Implementierung wird jemals Backtracking verwenden. Alle realen Mustervergleicher sind extrem schnell - nahezu unabhängig von der Komplexität des regulären Ausdrucks!
quelle
Bei der gierigen Quantifizierung wird während einer Iteration ein Mustervergleich unter Verwendung aller verbleibenden nicht validierten Zeichen einer Zeichenfolge durchgeführt. Nicht validierte Zeichen beginnen in der aktiven Sequenz . Jedes Mal, wenn keine Übereinstimmung auftritt, wird das Zeichen am Ende unter Quarantäne gestellt und die Prüfung erneut durchgeführt.
Wenn nur die führenden Bedingungen des Regex-Musters von der aktiven Sequenz erfüllt werden, wird versucht, die verbleibenden Bedingungen gegen die Quarantäne zu validieren. Wenn diese Validierung erfolgreich ist, werden übereinstimmende Zeichen in der Quarantäne validiert und verbleibende nicht übereinstimmende Zeichen bleiben nicht validiert und werden verwendet, wenn der Prozess in der nächsten Iteration erneut beginnt.
Der Zeichenfluss erfolgt von der aktiven Sequenz in die Quarantäne. Das resultierende Verhalten ist, dass so viel wie möglich von der ursprünglichen Sequenz in einem Match enthalten ist.
Die zögernde Quantifizierung ist meistens dasselbe wie die gierige Qualifizierung, außer dass der Zeichenfluss das Gegenteil ist - das heißt, sie beginnen in der Quarantäne und fließen in die aktive Sequenz . Das resultierende Verhalten ist, dass so wenig wie möglich von der ursprünglichen Sequenz in einem Match enthalten ist.
Possessive Quantification hat keine Quarantäne und enthält alles in einer festen aktiven Sequenz .
quelle