Was ist der Unterschied zwischen "Gruppen" und "Captures" in regulären .NET-Ausdrücken?

161

Ich bin mir ein wenig unklar, was der Unterschied zwischen einer "Gruppe" und einem "Capture" ist, wenn es um die reguläre Ausdruckssprache von .NET geht. Betrachten Sie den folgenden C # -Code:

MatchCollection matches = Regex.Matches("{Q}", @"^\{([A-Z])\}$");

Ich erwarte, dass dies zu einer einzigen Erfassung für den Buchstaben 'Q' führt, aber wenn ich die Eigenschaften der zurückgegebenen Datei drucke MatchCollection, sehe ich:

matches.Count: 1
matches[0].Value: {Q}
        matches[0].Captures.Count: 1
                matches[0].Captures[0].Value: {Q}
        matches[0].Groups.Count: 2
                matches[0].Groups[0].Value: {Q}
                matches[0].Groups[0].Captures.Count: 1
                        matches[0].Groups[0].Captures[0].Value: {Q}
                matches[0].Groups[1].Value: Q
                matches[0].Groups[1].Captures.Count: 1
                        matches[0].Groups[1].Captures[0].Value: Q

Was genau ist hier los? Ich verstehe, dass es auch eine Erfassung für das gesamte Spiel gibt, aber wie kommen die Gruppen herein? Und warum ist matches[0].Capturesdie Erfassung für den Buchstaben "Q" nicht enthalten?

Nick Meyer
quelle

Antworten:

126

Sie werden nicht der erste sein, der sich darüber im Klaren ist. Hier ist, was der berühmte Jeffrey Friedl dazu zu sagen hat (Seiten 437+):

Abhängig von Ihrer Ansicht fügt es den Spielergebnissen entweder eine interessante neue Dimension hinzu oder sorgt für Verwirrung und Aufblähung.

Und weiter:

Der Hauptunterschied zwischen einem Gruppenobjekt und einem Erfassungsobjekt besteht darin, dass jedes Gruppenobjekt eine Sammlung von Erfassungen enthält, die alle Zwischenübereinstimmungen der Gruppe während der Übereinstimmung sowie den endgültigen Text der Gruppe darstellen.

Und ein paar Seiten später ist dies seine Schlussfolgerung:

Nachdem ich die .NET-Dokumentation hinter mich gebracht und verstanden habe, was diese Objekte hinzufügen, habe ich gemischte Gefühle in Bezug auf sie. Einerseits ist es eine interessante Innovation [..], andererseits scheint es eine Effizienzbelastung [..] einer Funktionalität hinzuzufügen, die in den meisten Fällen nicht verwendet wird

Mit anderen Worten: Sie sind sich sehr ähnlich, aber gelegentlich und wenn es passiert, werden Sie eine Verwendung für sie finden. Bevor Sie sich einen weiteren grauen Bart wachsen lassen, werden Sie vielleicht sogar die Captures lieben ...


Da weder das oben Gesagte noch das, was in dem anderen Beitrag gesagt wird, Ihre Frage wirklich zu beantworten scheint, sollten Sie Folgendes berücksichtigen. Stellen Sie sich Captures als eine Art Verlaufs-Tracker vor. Wenn der reguläre Ausdruck übereinstimmt, durchläuft er die Zeichenfolge von links nach rechts (wobei das Zurückverfolgen für einen Moment ignoriert wird). Wenn er auf übereinstimmende Erfassungsklammern stößt, wird dieser in $x(x ist eine beliebige Ziffer) gespeichert $1.

Normale Regex-Engines werfen den Strom weg $1und ersetzen ihn durch den neuen Wert , wenn die Erfassungsklammern wiederholt werden sollen . Nicht .NET, das diesen Verlauf beibehält und platziert Captures[0].

Wenn wir Ihre Regex wie folgt ändern:

MatchCollection matches = Regex.Matches("{Q}{R}{S}", @"(\{[A-Z]\})+");

Sie werden feststellen, dass die erste Groupeine hat Captures(die erste Gruppe ist immer die gesamte Übereinstimmung, dh gleich $0), und die zweite Gruppe hält {S}, dh nur die letzte übereinstimmende Gruppe. Und hier ist der Haken: Wenn Sie die beiden anderen Fänge finden möchten, befinden sie sich in Captures, die alle Zwischenerfassungen für {Q} {R}und enthalten {S}.

Wenn Sie sich jemals gefragt haben, wie Sie von der Mehrfachaufnahme profitieren können, bei der nur die letzte Übereinstimmung mit den einzelnen Aufnahmen angezeigt wird, die eindeutig in der Zeichenfolge enthalten sind, müssen Sie diese verwenden Captures.

Ein letztes Wort zu Ihrer letzten Frage: Die Gesamtübereinstimmung hat immer eine Gesamtaufnahme. Mischen Sie diese nicht mit den einzelnen Gruppen. Aufnahmen sind nur innerhalb von Gruppen interessant .

Abel
quelle
1
a functionality that won't be used in the majority of casesIch glaube, er hat das Boot verpasst. Kurzfristig (?:.*?(collection info)){4,20}erhöht sich die Effizienz um mehr als einige hundert Prozent.
1
@sln, nicht sicher, worauf Sie sich beziehen und wer "er" ist (friedl?). Das Beispiel, das Sie geben, scheint nichts mit dieser Diskussion oder den verwendeten Ausdrücken zu tun zu haben. Außerdem sind nicht gierige Quantifizierer nur sehr selten effizienter als gierige Quantifizierer und erfordern Kenntnisse des Eingabesatzes und sorgfältige Perfektionstests.
Abel
@Abel - Ich bin hier von einer Frage gelandet, die als Duplikat davon markiert ist. Ich sehe Friedl zitiert. Dieser Beitrag ist alt und muss aktualisiert werden, um ihn modern zu halten. Nur mit Dot Net ist dies möglich. Es unterscheidet sich von den meisten anderen. Aufschlüsselung: Ein quantifiziertes Beispiel für eine nicht erfasste Gesamtgruppe (?:..)+. Ordnen Sie alles faul .*?einem Capture-Unterausdruck (Gruppe) zu. Weitermachen mit. Innerhalb eines einzelnen Spiels führt eine Gruppensammlung eine Reihe von Informationen aus. Es besteht keine Notwendigkeit, als nächstes zu finden, es gibt keinen Wiedereintritt, wodurch es 10 bis 20 Mal oder öfter schneller wird.
1
@sln, diese Frage bezieht sich auf etwas anderes und speziell auf eine .net-Funktion, die in anderen Regex-Engines nicht zu finden ist (Gruppen gegen Erfassungen, siehe Titel). Ich sehe hier nichts Veraltetes, .net funktioniert immer noch genauso, tatsächlich hat sich dieser Teil in .net lange nicht geändert. Leistung ist nicht Teil der Frage. Ja, nicht erfasste Gruppierungen sind schneller, aber auch hier ist das Thema das Gegenteil. Warum Gier schneller als Faul ist, wird in vielen Texten online und in Friedls Buch erklärt, aber OT hier. Vielleicht war die andere Frage (welche?) Kein echtes Duplikat?
Abel
2
@Abel - Ich weiß, ich sage es immer wieder, aber du hörst es immer wieder nicht. Ich nehme Anstoß zu dieser Aussage von Friedl a functionality that won't be used in the majority of cases. Tatsächlich ist es die am meisten nachgefragte Funktionalität im Regex-Land. Faul / gierig? Was hat das mit meinen Kommentaren zu tun? Es ermöglicht eine variable Anzahl von Erfassungspuffern. Es kann die gesamte Zeichenfolge in einem einzigen Match fegen. Wenn .*?(dog)Funde des ersten dogdann (?:.*?(dog))+finden alle dog in der gesamten Zeichenfolge in einem Einzelspiel. Die Leistungssteigerung macht sich bemerkbar.
20

Eine Gruppe ist das, was wir Gruppen in regulären Ausdrücken zugeordnet haben

"(a[zx](b?))"

Applied to "axb" returns an array of 3 groups:

group 0: axb, the entire match.
group 1: axb, the first group matched.
group 2: b, the second group matched.

außer dass dies nur "erfasste" Gruppen sind. Nicht erfassende Gruppen (mit der Syntax '(?:') Werden hier nicht dargestellt.

"(a[zx](?:b?))"

Applied to "axb" returns an array of 2 groups:

group 0: axb, the entire match.
group 1: axb, the first group matched.

Eine Erfassung ist auch das, was wir mit "erfassten Gruppen" verknüpft haben. Wenn die Gruppe jedoch mehrmals mit einem Quantifizierer angewendet wird, wird nur die letzte Übereinstimmung als Übereinstimmung der Gruppe beibehalten. Das Captures-Array speichert alle diese Übereinstimmungen.

"(a[zx]\s+)+"

Applied to "ax az ax" returns an array of 2 captures of the second group.

group 1, capture 0 "ax "
group 1, capture 1 "az "

Was Ihre letzte Frage betrifft - ich hätte gedacht, bevor ich mich damit befasste, dass Captures eine Reihe von Captures sind, die nach der Gruppe geordnet sind, zu der sie gehören. Es ist vielmehr nur ein Alias ​​für die Gruppen [0] .Captures. Ziemlich nutzlos ..

Gerard ONeill
quelle
Klare Erklärung (y)
Ghasan
18

Dies kann anhand eines einfachen Beispiels (und von Bildern) erklärt werden.

Übereinstimmung 3:10pmmit dem regulären Ausdruck ((\d)+):((\d)+)(am|pm)und Verwendung von Mono Interactive csharp:

csharp> Regex.Match("3:10pm", @"((\d)+):((\d)+)(am|pm)").
      > Groups.Cast<Group>().
      > Zip(Enumerable.Range(0, int.MaxValue), (g, n) => "[" + n + "] " + g);
{ "[0] 3:10pm", "[1] 3", "[2] 3", "[3] 10", "[4] 0", "[5] pm" }

Wo ist die 1? Geben Sie hier die Bildbeschreibung ein

Da es mehrere Ziffern gibt, die mit der vierten Gruppe übereinstimmen, "erreichen" wir die letzte Übereinstimmung nur, wenn wir auf die Gruppe verweisen (dh implizit ToString()). Um die Zwischenübereinstimmungen aufzudecken, müssen wir tiefer gehen und auf die CapturesEigenschaft der betreffenden Gruppe verweisen :

csharp> Regex.Match("3:10pm", @"((\d)+):((\d)+)(am|pm)").
      > Groups.Cast<Group>().
      > Skip(4).First().Captures.Cast<Capture>().
      > Zip(Enumerable.Range(0, int.MaxValue), (c, n) => "["+n+"] " + c);
{ "[0] 1", "[1] 0" }

Geben Sie hier die Bildbeschreibung ein

Mit freundlicher Genehmigung dieses Artikels .

Eric Smith
quelle
3
Schöner Artikel. Ein Bild sagt mehr als tausend Worte.
AlexWei
Du bist ein Star.
Mikemay
14

Aus der MSDN- Dokumentation :

Der eigentliche Nutzen der Captures-Eigenschaft liegt vor, wenn ein Quantifizierer auf eine Erfassungsgruppe angewendet wird, sodass die Gruppe mehrere Teilzeichenfolgen in einem einzigen regulären Ausdruck erfasst. In diesem Fall enthält das Group-Objekt Informationen zum zuletzt erfassten Teilstring, während die Captures-Eigenschaft Informationen zu allen von der Gruppe erfassten Teilzeichenfolgen enthält. Im folgenden Beispiel der reguläre Ausdruck \ b (\ w + \ s *) +. entspricht einem ganzen Satz, der in einem Punkt endet. Die Gruppe (\ w + \ s *) + erfasst die einzelnen Wörter in der Sammlung. Da die Gruppensammlung nur Informationen über den zuletzt erfassten Teilstring enthält, erfasst sie das letzte Wort im Satz "Satz". Jedes von der Gruppe erfasste Wort ist jedoch aus der Sammlung verfügbar, die von der Captures-Eigenschaft zurückgegeben wird.

pmarflee
quelle
4

Stellen Sie sich vor, Sie haben die folgende Texteingabe dogcatcatcatund ein Muster wiedog(cat(catcat))

In diesem Fall haben Sie 3 Gruppen, die erste ( Hauptgruppe ) entspricht der Übereinstimmung.

Match == dogcatcatcatund Group0 ==dogcatcatcat

Gruppe1 == catcatcat

Gruppe2 == catcat

Worum geht es also?

Betrachten wir ein kleines Beispiel, das in C # (.NET) mit Regexclass geschrieben wurde.

int matchIndex = 0;
int groupIndex = 0;
int captureIndex = 0;

foreach (Match match in Regex.Matches(
        "dogcatabcdefghidogcatkjlmnopqr", // input
        @"(dog(cat(...)(...)(...)))") // pattern
)
{
    Console.Out.WriteLine($"match{matchIndex++} = {match}");

    foreach (Group @group in match.Groups)
    {
        Console.Out.WriteLine($"\tgroup{groupIndex++} = {@group}");

        foreach (Capture capture in @group.Captures)
        {
            Console.Out.WriteLine($"\t\tcapture{captureIndex++} = {capture}");
        }

        captureIndex = 0;
    }

    groupIndex = 0;
    Console.Out.WriteLine();
        }

Ausgabe :

match0 = dogcatabcdefghi
    group0 = dogcatabcdefghi
        capture0 = dogcatabcdefghi
    group1 = dogcatabcdefghi
        capture0 = dogcatabcdefghi
    group2 = catabcdefghi
        capture0 = catabcdefghi
    group3 = abc
        capture0 = abc
    group4 = def
        capture0 = def
    group5 = ghi
        capture0 = ghi

match1 = dogcatkjlmnopqr
    group0 = dogcatkjlmnopqr
        capture0 = dogcatkjlmnopqr
    group1 = dogcatkjlmnopqr
        capture0 = dogcatkjlmnopqr
    group2 = catkjlmnopqr
        capture0 = catkjlmnopqr
    group3 = kjl
        capture0 = kjl
    group4 = mno
        capture0 = mno
    group5 = pqr
        capture0 = pqr

Lassen Sie uns nur das erste Match analysieren ( match0).

Wie Sie sehen können , gibt es drei kleinere Gruppen : group3, group4undgroup5

    group3 = kjl
        capture0 = kjl
    group4 = mno
        capture0 = mno
    group5 = pqr
        capture0 = pqr

Diese Gruppen (3-5) wurden aufgrund des " Untermusters " (...)(...)(...)des Hauptmusters erstellt (dog(cat(...)(...)(...)))

Der Wert von group3entspricht der Erfassung ( capture0). (Wie im Fall von group4und group5). Das liegt daran, dass es keine Gruppenwiederholungen wie gibt (...){3}.


Ok, betrachten wir ein anderes Beispiel, in dem es eine Gruppenwiederholung gibt .

Wenn wir das Muster für reguläre Ausdrücke (für den oben gezeigten Code) von (dog(cat(...)(...)(...)))bis ändern (dog(cat(...){3})), werden Sie feststellen, dass es die folgende Gruppenwiederholung gibt : (...){3}.

Jetzt hat sich die Ausgabe geändert:

match0 = dogcatabcdefghi
    group0 = dogcatabcdefghi
        capture0 = dogcatabcdefghi
    group1 = dogcatabcdefghi
        capture0 = dogcatabcdefghi
    group2 = catabcdefghi
        capture0 = catabcdefghi
    group3 = ghi
        capture0 = abc
        capture1 = def
        capture2 = ghi

match1 = dogcatkjlmnopqr
    group0 = dogcatkjlmnopqr
        capture0 = dogcatkjlmnopqr
    group1 = dogcatkjlmnopqr
        capture0 = dogcatkjlmnopqr
    group2 = catkjlmnopqr
        capture0 = catkjlmnopqr
    group3 = pqr
        capture0 = kjl
        capture1 = mno
        capture2 = pqr

Lassen Sie uns noch einmal nur das erste Match analysieren ( match0).

Es gibt keine kleineren Gruppen mehr group4 und group5aufgrund von (...){3} Wiederholungen ( {n} wobei n> = 2 ) wurden sie zu einer einzigen Gruppe zusammengeführt group3.

In diesem Fall group3entspricht der Wert dem Wert capture2(mit anderen Worten der letzten Erfassung ).

Wenn Sie also alle 3 inneren Captures (,, ) benötigen capture0, capture1müssen capture2Sie die CapturesSammlung der Gruppe durchlaufen .

Fazit: Achten Sie darauf, wie Sie die Gruppen Ihres Musters entwerfen. Sie sollten denken , im Voraus , was Verhalten Gruppe Spezifikation verursacht, wie (...)(...), (...){2}oder (.{3}){2}usw.


Hoffentlich hilft es dabei, die Unterschiede zwischen Captures , Gruppen und Matches zu beleuchten .

AlexMelw
quelle