Jaka jest różnica między „grupami” a „przechwytywaniami” w wyrażeniach regularnych platformy .NET?


161

Jestem trochę niejasny co do różnicy między „grupą” a „przechwytywaniem”, jeśli chodzi o język wyrażeń regularnych .NET. Rozważmy następujący kod C #:

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

Spodziewam się, że spowoduje to pojedyncze przechwycenie litery `` Q '', ale jeśli wydrukuję właściwości zwróconego MatchCollection, widzę:

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

Co tu się właściwie dzieje? Rozumiem, że jest również przechwytywanie całego meczu, ale w jaki sposób grupy wchodzą? I dlaczego nie matches[0].Capturesobejmuje przechwycenia litery „Q”?

Odpowiedzi:


126

Nie będziesz pierwszym, który się o tym myśli. Oto, co na ten temat ma do powiedzenia słynny Jeffrey Friedl (strony 437+):

W zależności od twojego poglądu, albo dodaje interesujący nowy wymiar do wyników meczu, albo dodaje zamieszania i wzdęcia.

I dalej:

Główna różnica między obiektem Group a obiektem Capture polega na tym, że każdy obiekt Group zawiera kolekcję przechwyceń reprezentujących wszystkie pośrednie dopasowania grupy podczas dopasowania, a także ostateczny tekst dopasowany przez grupę.

A kilka stron dalej, oto jego wniosek:

Po obejrzeniu dokumentacji .NET i zrozumieniu, co dodają te obiekty, mam co do nich mieszane uczucia. Z jednej strony jest to interesująca innowacja […], z drugiej strony wydaje się, że zwiększa wydajność […] funkcjonalności, która nie będzie używana w większości przypadków

Innymi słowy: są bardzo podobne, ale czasami i tak się składa, że ​​znajdziesz dla nich zastosowanie. Zanim zapuścisz kolejną siwą brodę, możesz nawet polubić Capture ...


Ponieważ ani powyższe, ani to, co zostało powiedziane w innym poście, nie wydaje się odpowiadać na Twoje pytanie, rozważ następujące kwestie. Potraktuj Captures jako swego rodzaju narzędzie do śledzenia historii. Kiedy wyrażenie regularne dopasowuje się, przechodzi przez ciąg od lewej do prawej (ignorując na chwilę cofanie się), a kiedy napotka pasujące nawiasy przechwytujące, zapisze to w $x(powiedzmy, że x jest dowolną cyfrą) $1.

Normalne silniki wyrażeń regularnych, gdy mają zostać powtórzone nawiasy przechwytujące, odrzucą bieżący $1i zastąpią go nową wartością. Nie .NET, który zachowa tę historię i umieści ją w Captures[0].

Jeśli zmienimy Twoje wyrażenie regularne, aby wyglądało następująco:

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

Zauważysz, że pierwsza Groupbędzie miała jedną Captures(pierwsza grupa to zawsze cały mecz, czyli równa się $0), a druga grupa {S}, czyli tylko ostatnia pasująca grupa. Jednak, a oto haczyk, jeśli chcesz znaleźć pozostałe dwa haczyki, są one w Captures, które zawierają wszystkie pośrednie przechwytywania dla {Q} {R}i{S} .

Jeśli kiedykolwiek zastanawiałeś się, w jaki sposób można uzyskać z wielokrotnego przechwytywania, które pokazuje tylko ostatnie dopasowanie do pojedynczych przechwyceń, które są wyraźnie widoczne w ciągu, musisz użyć Captures .

Ostatnie słowo na twoje ostatnie pytanie: całkowite dopasowanie zawsze ma jedno całkowite przejęcie, nie mieszaj tego z poszczególnymi grupami. Zrzuty są interesujące tylko w grupach .


1
a functionality that won't be used in the majority of casesMyślę, że przegapił łódź. W krótkim okresie (?:.*?(collection info)){4,20}zwiększa efektywność o kilkaset procent.

1
@sln, nie jestem pewien, o czym mówisz i kim on jest (Friedl?). Podany przykład wydaje się niezwiązany z tą dyskusją ani z używanymi wyrażeniami. Poza tym niechciwe kwantyfikatory tylko bardzo rzadko są bardziej wydajne niż chciwe kwantyfikatory i wymagają znajomości zbioru danych wejściowych oraz dokładnego testowania wydajności.
Abel

@Abel - wylądowałem tutaj z pytania oznaczonego jako duplikat tego. Widzę cytat Friedla. Ten post jest stary i wymaga odświeżenia, aby był nowoczesny. Tylko z Dot Net można to zrobić, to odróżnia go od większości innych. Podział: ogólna przykładowa grupa bez przechwytywania (?:..)+. Łagodnie dopasuj wszystko .*?do wyrażenia podrzędnego przechwytywania (grupy). Kontynuuj. W ramach jednego dopasowania zbiór grupowy generuje tablicę dokładnie tego, co jest potrzebne. Nie ma potrzeby znajdowania następnego, nie ma ponownego wejścia, dzięki czemu jest 10 do 20 lub więcej razy szybciej.

1
@sln, to pytanie dotyczy czegoś innego, a konkretnie funkcji .net, której nie można znaleźć w innych silnikach regex (grupy a przechwytywania, patrz tytuł). Nie widzę tu nic nieaktualnego, .net nadal działa tak samo, w rzeczywistości ta część nie zmieniła się od dłuższego czasu w .net. Wydajność nie jest częścią pytania. Tak, grupowanie bez przechwytywania jest szybsze, ale znowu temat jest odwrotny. Dlaczego chciwość jest szybsza niż leniwa, jest wyjaśniona w wielu tekstach online i w książce Friedla, ale OT tutaj. Może drugie pytanie (które?) Nie było prawdziwym duplikatem?
Abel

2
@Abel - Wiem, że ciągle to mówię, ale ty tego nie słyszysz. Jestem urażony tą wypowiedzią Friedla a functionality that won't be used in the majority of cases. W rzeczywistości jest to najbardziej poszukiwana funkcja w krainie wyrażeń regularnych. Leniwy / chciwy? Co to ma wspólnego z moimi komentarzami? Umożliwia to posiadanie zmiennej ilości buforów przechwytywania. Może zamiatać cały ciąg w jednym meczu. Jeśli .*?(dog)znajdzie pierwszy dogwtedy (?:.*?(dog))+znajdzie wszystko dog w cały ciąg w jednym meczu. Wzrost wydajności jest zauważalny.

20

Grupa jest tym, co skojarzyliśmy z grupami w wyrażeniach regularnych

"(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.

poza tym, że są to tylko grupy „przechwycone”. Grupy bez przechwytywania (przy użyciu składni „(?:”) Nie są tutaj reprezentowane.

"(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.

Przechwytywanie jest również tym, co skojarzyliśmy z „przechwyconymi grupami”. Ale gdy grupa jest wielokrotnie stosowana z kwantyfikatorem, tylko ostatnie dopasowanie jest zachowywane jako dopasowanie grupy. Tablica przechwytywania przechowuje wszystkie te dopasowania.

"(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 "

A jeśli chodzi o twoje ostatnie pytanie - zanim się nad tym zastanowiłem, pomyślałem, że przechwytywania będą tablicą ujęć uporządkowanych według grupy, do której należą. Jest to raczej alias do grup [0] .Przechwytuje. Całkiem bezużyteczne ...


Jasne wyjaśnienie (y)
Ghasan

19

Można to wytłumaczyć prostym przykładem (i ilustracjami).

Dopasowanie 3:10pmdo wyrażenia regularnego ((\d)+):((\d)+)(am|pm)i użycie interaktywnej Mono 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" }

Więc gdzie jest 1? wprowadź opis obrazu tutaj

Ponieważ istnieje wiele cyfr pasujących do czwartej grupy, „uzyskujemy” tylko ostatnie dopasowanie, jeśli odwołujemy się do grupy (to ToString()znaczy niejawnie ). Aby ujawnić dopasowania pośrednie, musimy wejść głębiej i odwołać się do Captureswłaściwości w danej grupie:

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" }

wprowadź opis obrazu tutaj

Dzięki uprzejmości tego artykułu .


3
Niezły artykuł. Obraz jest wart tysiąca słów.
AlexWei,

Jesteś gwiazdą.
mikemay

14

Z dokumentacji MSDN :

Rzeczywista użyteczność właściwości Captures występuje, gdy kwantyfikator jest stosowany do grupy przechwytywania, tak że grupa przechwytuje wiele podciągów w jednym wyrażeniu regularnym. W tym przypadku obiekt Group zawiera informacje o ostatnio przechwyconym podciągu, podczas gdy właściwość Captures zawiera informacje o wszystkich podciągach przechwyconych przez grupę. W poniższym przykładzie wyrażenie regularne \ b (\ w + \ s *) +. dopasowuje całe zdanie, które kończy się kropką. Grupa (\ w + \ s *) + przechwytuje poszczególne słowa w kolekcji. Ponieważ kolekcja Group zawiera informacje tylko o ostatnio przechwyconym podciągu, przechwytuje ostatnie słowo w zdaniu, „zdanie”. Jednak każde słowo przechwycone przez grupę jest dostępne z kolekcji zwróconej przez właściwość Captures.


4

Wyobraź sobie, że wprowadzasz następujący tekst dogcatcatcat i wzór podobny dodog(cat(catcat))

W tym przypadku masz 3 grupy, pierwszą ( główną ) odpowiada dopasowaniu.

Dopasuj == dogcatcatcat i Grupa0 ==dogcatcatcat

Grupa1 == catcatcat

Grupa2 == catcat

Więc o co w tym wszystkim chodzi?

Rozważmy mały przykład napisany w C # (.NET) przy użyciu Regexclass.

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();
        }

Wyjście :

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

Przeanalizujmy tylko pierwsze dopasowanie (match0 ).

Jak widać istnieją trzy grupy drobne : group3, group4igroup5

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

Te grupy (3-5) powstały z powodu „ podciąg wzorca(...)(...)(...)w głównej strukturze (dog(cat(...)(...)(...)))

Wartość group3odpowiada jego przechwytywaniu ( capture0). (Jak w przypadku group4i group5). To dlatego, że nie ma takich powtórzeń grupowych(...){3} .


Ok, rozważmy inny przykład, w którym występuje powtórzenie grupowe .

Jeśli zmodyfikujemy wzorzec wyrażenia regularnego, który ma być dopasowany (dla kodu pokazanego powyżej) od (dog(cat(...)(...)(...)))do (dog(cat(...){3})), zauważysz, że występuje następujące powtórzenie grupy :(...){3} .

Teraz wyjściowa uległa zmianie:

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

Ponownie przeanalizujmy tylko pierwsze dopasowanie (match0 ).

Nie ma już mniejszych grup group4 iz group5powodu (...){3} powtórzeń ( {n} gdzie n> = 2 ) zostały połączone w jedną grupęgroup3 .

W tym przypadku group3wartość odpowiada it capture2( ostatnie przechwycenie innymi słowy ).

Tak więc, jeśli chcesz wszystkie 3 wewnętrzne oddaje ( capture0, capture1, capture2) będziesz musiał przechodzić GRUPYCaptures kolekcji.

Wniosek jest następujący: zwróć uwagę na sposób projektowania grup wzoru. Należy pomyśleć z góry, co powoduje zachowanie specyfikacji grupy, jak (...)(...), (...){2}lub (.{3}){2}etc.


Miejmy nadzieję, że pomoże to rzucić nieco światła na różnice między przejęciami , grupami i meczami .

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.