Was sind Balancing Groups mit regulären Ausdrücken?


90

Ich habe gerade eine Frage gelesen, wie man Daten in doppelte geschweifte Klammern bekommt ( diese Frage ), und dann hat jemand Ausgleichsgruppen angesprochen. Ich bin mir immer noch nicht ganz sicher, was sie sind und wie man sie benutzt.

Ich habe die Definition der Ausgleichsgruppe durchgelesen , aber die Erklärung ist schwer zu befolgen, und ich bin immer noch ziemlich verwirrt über die Fragen, die ich erwähnt habe.

Könnte jemand einfach erklären, was Ausgleichsgruppen sind und wie sie nützlich sind?


Ich frage mich, wie viele Regex-Engiens dies tatsächlich unterstützt.
Mike de Klerk

2
@MikedeKlerk Es wird mindestens in der .NET Regex-Engine unterstützt.
wahr.

Antworten:


171

Soweit ich weiß, sind Ausgleichsgruppen für die Regex-Variante von .NET einzigartig.

Nebenbei: Wiederholte Gruppen

Zunächst müssen Sie wissen, dass .NET (soweit ich weiß) die einzige Regex-Variante ist, mit der Sie auf mehrere Erfassungen einer einzelnen Erfassungsgruppe zugreifen können (nicht in Rückreferenzen, sondern nach Abschluss des Spiels).

Um dies anhand eines Beispiels zu veranschaulichen, betrachten Sie das Muster

(.)+

und die Zeichenfolge "abcd".

Bei allen anderen Regex-Varianten führt die Erfassungsgruppe 1lediglich zu einem Ergebnis: d(Beachten Sie, dass die vollständige Übereinstimmung natürlich abcdwie erwartet erfolgt.) Dies liegt daran, dass bei jeder neuen Verwendung der Erfassungsgruppe die vorherige Erfassung überschrieben wird.

.NET hingegen erinnert sich an alle. Und das in einem Stapel. Nach dem Abgleichen der obigen Regex wie

Match m = new Regex(@"(.)+").Match("abcd");

Sie werden das finden

m.Groups[1].Captures

Ist ein, CaptureCollectiondessen Elemente den vier Erfassungen entsprechen

0: "a"
1: "b"
2: "c"
3: "d"

Dabei ist die Nummer der Index in der CaptureCollection. Grundsätzlich wird jedes Mal, wenn die Gruppe erneut verwendet wird, eine neue Erfassung auf den Stapel verschoben.

Interessanter wird es, wenn wir benannte Erfassungsgruppen verwenden. Da .NET die wiederholte Verwendung des gleichen Namens ermöglicht, können wir einen regulären Ausdruck wie schreiben

(?<word>\w+)\W+(?<word>\w+)

um zwei Wörter in derselben Gruppe zu erfassen. Jedes Mal, wenn eine Gruppe mit einem bestimmten Namen angetroffen wird, wird eine Erfassung auf ihren Stapel verschoben. Wenden Sie diesen regulären Ausdruck also auf die Eingabe an "foo bar"und überprüfen Sie ihn

m.Groups["word"].Captures

Wir finden zwei Aufnahmen

0: "foo"
1: "bar"

Auf diese Weise können wir sogar Dinge aus verschiedenen Teilen des Ausdrucks auf einen einzelnen Stapel verschieben. Dies ist jedoch nur die Funktion von .NET, mit der mehrere in dieser Liste aufgeführte Aufnahmen verfolgt werden können CaptureCollection. Aber ich sagte, diese Sammlung ist ein Stapel . So können wir knallen Dinge von ihm?

Geben Sie ein: Gruppen ausgleichen

Es stellt sich heraus, dass wir es können. Wenn wir eine Gruppe wie verwenden (?<-word>...), wird die letzte Erfassung vom Stapel entfernt, wordwenn der Unterausdruck ...übereinstimmt. Wenn wir also unseren vorherigen Ausdruck in ändern

(?<word>\w+)\W+(?<-word>\w+)

Dann wird die zweite Gruppe die Aufnahme der ersten Gruppe platzen lassen und wir werden CaptureCollectionam Ende eine leere erhalten . Natürlich ist dieses Beispiel ziemlich nutzlos.

Die Minus-Syntax enthält jedoch noch ein weiteres Detail: Wenn der Stapel bereits leer ist, schlägt die Gruppe fehl (unabhängig von ihrem Untermuster). Wir können dieses Verhalten nutzen, um Verschachtelungsebenen zu zählen - und hier kommt die Namensausgleichsgruppe her (und dort wird es interessant). Angenommen, wir möchten Zeichenfolgen abgleichen, die korrekt in Klammern stehen. Wir schieben jede öffnende Klammer auf den Stapel und fügen für jede schließende Klammer eine Aufnahme hinzu. Wenn eine schließende Klammer zu viele enthält, wird versucht, einen leeren Stapel zu platzieren, und das Muster schlägt fehl:

^(?:[^()]|(?<Open>[(])|(?<-Open>[)]))*$

Wir haben also drei Alternativen in einer Wiederholung. Die erste Alternative verbraucht alles, was keine Klammer ist. Die zweite Alternative entspricht (s, während sie auf den Stapel geschoben werden. Die dritte Alternative stimmt mit )s überein, während Elemente vom Stapel entfernt werden (falls möglich!).

Hinweis: Zur Verdeutlichung überprüfen wir nur, dass keine nicht übereinstimmenden Klammern vorhanden sind! Dies bedeutet, dass Zeichenfolgen, die überhaupt keine Klammern enthalten , übereinstimmen, da sie syntaktisch noch gültig sind (in einer Syntax, in der Ihre Klammern übereinstimmen müssen). Wenn Sie mindestens einen Satz Klammern sicherstellen möchten, fügen Sie einfach (?=.*[(])direkt nach dem einen Lookahead hinzu ^.

Dieses Muster ist jedoch nicht perfekt (oder völlig korrekt).

Finale: Bedingte Muster

Es gibt noch einen Haken: Dies stellt nicht sicher, dass der Stapel am Ende der Zeichenfolge leer ist (daher (foo(bar)gültig). .NET (und viele andere Varianten) haben ein weiteres Konstrukt, das uns hier hilft: bedingte Muster. Die allgemeine Syntax lautet

(?(condition)truePattern|falsePattern)

wo das falsePatternoptional ist - wenn es weggelassen wird, stimmt der falsche Fall immer überein. Die Bedingung kann entweder ein Muster oder der Name einer Erfassungsgruppe sein. Ich werde mich hier auf den letzteren Fall konzentrieren. Wenn es sich um den Namen einer Erfassungsgruppe handelt, truePatternwird dieser nur dann verwendet, wenn der Erfassungsstapel für diese bestimmte Gruppe nicht leer ist. Das heißt, ein bedingtes Muster wie (?(name)yes|no)"Wenn nameetwas übereinstimmt und erfasst wurde (das sich noch auf dem Stapel befindet), verwenden Sie ein Muster, yesandernfalls verwenden Sie ein Muster no".

Am Ende unseres obigen Musters könnten wir also so etwas hinzufügen, (?(Open)failPattern)was dazu führt, dass das gesamte Muster fehlschlägt, wenn der Open-stack nicht leer ist. Das Einfachste, um das Muster bedingungslos zum Scheitern zu bringen, ist (?!)(ein leerer negativer Lookahead). Wir haben also unser letztes Muster:

^(?:[^()]|(?<Open>[(])|(?<-Open>[)]))*(?(Open)(?!))$

Beachten Sie, dass diese bedingte Syntax per se nichts mit dem Ausgleich von Gruppen zu tun hat, sondern dass die volle Leistung genutzt werden muss.

Von hier aus ist der Himmel die Grenze. In Kombination mit anderen .NET-Regex-Funktionen wie Lookbehinds mit variabler Länge ( die ich selbst auf die harte Tour lernen musste) sind viele sehr anspruchsvolle Anwendungen möglich, und es gibt einige Fallstricke . Die Hauptfrage ist jedoch immer: Ist Ihr Code bei Verwendung dieser Funktionen noch wartbar? Sie müssen es wirklich gut dokumentieren und sicherstellen, dass jeder, der daran arbeitet, diese Funktionen auch kennt. Andernfalls ist es möglicherweise besser, wenn Sie die Zeichenfolge nur manuell Zeichen für Zeichen durchlaufen und die Verschachtelungsebenen in einer Ganzzahl zählen.

Nachtrag: Was ist mit der (?<A-B>...)Syntax?

Credits für diesen Teil gehen an Kobi (siehe seine Antwort unten für weitere Details).

Mit all dem können wir nun überprüfen, ob eine Zeichenfolge korrekt in Klammern steht. Aber es wäre viel nützlicher, wenn wir tatsächlich (verschachtelte) Captures für alle Inhalte dieser Klammern erhalten könnten. Natürlich können wir uns daran erinnern, Klammern in einem separaten Erfassungsstapel geöffnet und geschlossen zu haben, der nicht geleert wird, und dann in einem separaten Schritt eine Teilzeichenfolgenextraktion basierend auf ihren Positionen durchführen.

Aber .NET bietet hier noch eine weitere Komfortfunktion: Wenn wir verwenden (?<A-B>subPattern), wird nicht nur ein Capture vom Stapel genommen B, sondern auch alles zwischen diesem Popup von Bund dieser aktuellen Gruppe wird auf den Stapel verschoben A. Wenn wir also eine solche Gruppe für die schließenden Klammern verwenden, während wir Verschachtelungsebenen von unserem Stapel entfernen, können wir den Inhalt des Paares auch auf einen anderen Stapel verschieben:

^(?:[^()]|(?<Open>[(])|(?<Content-Open>[)]))*(?(Open)(?!))$

Kobi hat diese Live-Demo in seiner Antwort bereitgestellt

Wenn wir all diese Dinge zusammen nehmen, können wir:

  • Erinnere dich an beliebig viele Aufnahmen
  • Überprüfen Sie verschachtelte Strukturen
  • Erfassen Sie jede Verschachtelungsebene

Alles in einem einzigen regulären Ausdruck. Wenn das nicht aufregend ist ...;)

Einige Ressourcen, die ich hilfreich fand, als ich zum ersten Mal davon erfuhr:


6
Diese Antwort wurde zu den häufig gestellten Fragen zu Stapelüberlauf-regulären Ausdrücken unter "Advanced Regex-Fu" hinzugefügt .
Aliteralmind

39

Nur eine kleine Ergänzung zu M. Buettners hervorragender Antwort:

Was ist mit der (?<A-B>)Syntax los?

(?<A-B>x)unterscheidet sich subtil von (?<-A>(?<B>x)). Sie führen zum gleichen Kontrollfluss * , erfassen jedoch unterschiedlich.
Schauen wir uns zum Beispiel ein Muster für ausgewogene Zahnspangen an:

(?:[^{}]|(?<B>{)|(?<-B>}))+(?(B)(?!))

Am Ende des Spiels haben wir eine ausgeglichene Saite, aber das ist alles, was wir haben - wir wissen nicht, wo sich die Klammern befinden, weil der BStapel leer ist. Die harte Arbeit, die der Motor für uns geleistet hat, ist weg.
( Beispiel zu Regex Storm )

(?<A-B>x)ist die Lösung für dieses Problem. Wie? Es wird nicht erfasst xin $A: Es erfasst den Inhalt zwischen der vorherigen Erfassung Bund der aktuellen Position.

Verwenden wir es in unserem Muster:

(?:[^{}]|(?<Open>{)|(?<Content-Open>}))+(?(Open)(?!))

Dies würde $Contentfür jedes Paar auf dem Weg in die Saiten zwischen den Klammern (und ihren Positionen) erfassen .
Für die Saite {1 2 {3} {4 5 {6}} 7}würde es vier Aufnahmen sein: 3, 6, 4 5 {6}, und 1 2 {3} {4 5 {6}} 7- viel besser als nichts oder } } } }.
( Beispiel - Klicken Sie auf die tableRegisterkarte und schauen Sie sich an ${Content}, erfasst )

Tatsächlich kann es ohne Ausgleich verwendet werden: (?<A>).(.(?<Content-A>).)Erfasst die ersten beiden Zeichen, obwohl sie durch Gruppen getrennt sind.
(Ein Lookahead wird hier häufiger verwendet, skaliert jedoch nicht immer: Es kann Ihre Logik duplizieren.)

(?<A-B>)ist eine starke Funktion - sie gibt Ihnen die genaue Kontrolle über Ihre Aufnahmen. Denken Sie daran, wenn Sie versuchen, mehr aus Ihrem Muster herauszuholen.


@FYI, Fortsetzung der Diskussion von der Frage, die Ihnen in einer neuen Antwort auf diese Frage nicht gefallen hat . :)
zx81

Ich versuche herauszufinden, wie der Regex-Check für ausgewogene Klammern durchgeführt werden kann, indem Klammern innerhalb der Saiten entweichen. ZB wird der folgende Code übergeben: public class Foo {private const char BAR = '{'; private Zeichenfolge _qux = "{{{"; } Hat das jemand gemacht?
Herr Anderson

@ MrAnderson - Sie müssen nur an |'[^']*'der richtigen Stelle hinzufügen : Beispiel . Wenn Sie auch maskierte Zeichen benötigen, finden Sie hier ein Beispiel: (Regex für übereinstimmende C # -String- Literale) [ stackoverflow.com/a/4953878/7586] .
Kobi
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.