Qual è la differenza tra "gruppi" e "acquisizioni" nelle espressioni regolari .NET?


162

Sono un po 'confuso su quale sia la differenza tra un "gruppo" e una "cattura" quando si tratta del linguaggio delle espressioni regolari di .NET. Considera il seguente codice C #:

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

Mi aspetto che ciò comporti una singola acquisizione per la lettera 'Q', ma se stampo le proprietà del reso MatchCollection, vedo:

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

Cosa sta succedendo esattamente qui? Capisco che c'è anche una cattura per l'intera partita, ma come entrano i gruppi? E perché non matches[0].Capturesinclude l'acquisizione per la lettera 'Q'?

Risposte:


126

Non sarai il primo a essere confuso. Ecco cosa ha da dire il famoso Jeffrey Friedl (pagine 437+):

A seconda della vista, aggiunge una nuova dimensione interessante ai risultati della partita o aggiunge confusione e rigonfiamento.

E più avanti:

La differenza principale tra un oggetto Gruppo e un oggetto Capture è che ogni oggetto Group contiene una raccolta di Catture che rappresentano tutte le corrispondenze intermedie del gruppo durante la corrispondenza, nonché il testo finale corrispondente al gruppo.

E poche pagine dopo, questa è la sua conclusione:

Dopo aver superato la documentazione di .NET e aver effettivamente compreso cosa aggiungono questi oggetti, ho dei sentimenti contrastanti su di essi. Da un lato, è un'interessante innovazione [..] dall'altro, sembra aggiungere un onere di efficienza [..] di una funzionalità che non verrà utilizzata nella maggior parte dei casi

In altre parole: sono molto simili, ma di tanto in tanto e come succede, troverai un uso per loro. Prima di coltivare un'altra barba grigia, potresti persino appassionarti alle Catture ...


Dal momento che né quanto sopra, né quanto detto nell'altro post sembra davvero rispondere alla tua domanda, considera quanto segue. Pensa a Catture come una specie di tracker storico. Quando il regex fa la sua corrispondenza, passa attraverso la stringa da sinistra a destra (ignorando per un momento il backtracking) e quando incontra una parentesi di cattura corrispondente, la memorizzerà in $x(x essendo qualsiasi cifra), diciamo $1.

I normali motori regex, quando si devono ripetere le parentesi di cattura, gettano via la corrente $1e la sostituiscono con il nuovo valore. Non .NET, che manterrà questa storia e la inserirà Captures[0].

Se cambiamo il tuo regex per apparire come segue:

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

noterai che il primo Groupne avrà uno Captures(il primo gruppo sarà sempre l'intera corrispondenza, ovvero uguale a $0) e il secondo gruppo manterrà {S}, ovvero solo l'ultimo gruppo corrispondente. Tuttavia, ed ecco la cattura, se vuoi trovare le altre due catture, ci sono Captures, che contiene tutte le catture intermedie per {Q} {R}e {S}.

Se ti sei mai chiesto come potresti ottenere dalla cattura multipla, che mostra solo l'ultima corrispondenza alle singole acquisizioni che sono chiaramente lì nella stringa, devi usare Captures.

Un'ultima parola sulla tua domanda finale: la corrispondenza totale ha sempre una cattura totale, non mescolarla con i singoli gruppi. Le catture sono interessanti solo all'interno dei gruppi .


1
a functionality that won't be used in the majority of casesPenso che gli sia sfuggita la barca. A breve termine (?:.*?(collection info)){4,20}aumenta l'efficienza di qualche centinaio.

1
@sln, non sono sicuro di cosa ti riferisci e di chi sia "fritto". L'esempio che dai sembra non correlato a questa discussione o alle espressioni usate. Inoltre, i quantificatori non avidi sono molto raramente più efficienti dei quantificatori avidi e richiedono conoscenza del set di input e accurati test di perf.
Abele,

@Abel - Sono arrivato qui da una domanda contrassegnata come duplicata di questo. Vedo Friedl citato. Questo post è vecchio e deve essere aggiornato per mantenerlo moderno. Solo con Dot Net è possibile farlo, è ciò che lo separa dalla maggior parte degli altri. Suddivisione: un esempio di gruppo complessivo non acquisito quantificato (?:..)+. Abbina pigramente qualsiasi cosa .*?fino a un'espressione secondaria (gruppo) di acquisizione. Continua. All'interno di una singola partita, una raccolta di gruppi fa precipitare una serie di ciò che è necessario. Non c'è bisogno di trovare il prossimo, non c'è nessun rientro che lo rende 10 a 20 o più volte più veloce.

1
@sln, questa domanda riguarda qualcos'altro e riguarda in particolare una funzione .net non presente in altri motori regex (gruppi vs acquisizioni, vedi titolo). Qui non vedo nulla di obsoleto, .net funziona ancora allo stesso modo, infatti questa parte non è cambiata da molto tempo in .net. Le prestazioni non fanno parte della domanda. Sì, il raggruppamento non acquisito è più veloce, ma ancora una volta, il soggetto qui è l'opposto. Perché l'avido è più veloce del pigro è spiegato in molti testi online e dal libro di Friedl, ma OT qui. Forse l'altra domanda (quale?) Non era un vero duplicato?
Abele,

2
@Abel - So che continuo a dirlo, ma continui a non sentirlo. Prendo spunto da questa affermazione di Friedl a functionality that won't be used in the majority of cases. In effetti è la funzionalità più ricercata in regex land. Pigro / goloso? Cosa c'entra questo con i miei commenti? Consente di disporre di una quantità variabile di buffer di acquisizione. Può spazzare l'intera stringa in una singola corrispondenza. Se .*?(dog)trova il primo, dogallora (?:.*?(dog))+troverà tutto dog nell'intera stringa in una singola corrispondenza. L'aumento delle prestazioni è evidente.

20

Un gruppo è ciò che abbiamo associato ai gruppi in espressioni regolari

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

tranne che questi sono solo gruppi "catturati". Gruppi non acquisiti (utilizzando la sintassi '(?:' Non sono rappresentati qui.

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

Una cattura è anche ciò che abbiamo associato ai "gruppi acquisiti". Ma quando il gruppo viene applicato più volte con un quantificatore, solo l'ultima partita viene mantenuta come partita del gruppo. L'array di acquisizione memorizza tutte queste corrispondenze.

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

Per quanto riguarda la tua ultima domanda, avrei pensato prima di esaminare che le Catture sarebbero state una serie di catture ordinate dal gruppo a cui appartengono. Piuttosto è solo un alias per i gruppi [0] .Catture. Abbastanza inutile ..


Spiegazione chiara (y)
Ghasan

19

Questo può essere spiegato con un semplice esempio (e immagini).

Corrispondenza 3:10pmcon l'espressione regolare ((\d)+):((\d)+)(am|pm)e utilizzo di Mono interattivo 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" }

Allora dov'è l'1? inserisci qui la descrizione dell'immagine

Poiché ci sono più cifre che corrispondono al quarto gruppo, "arriviamo a" l'ultima corrispondenza solo se facciamo riferimento al gruppo (con un implicito ToString(), cioè). Per esporre le corrispondenze intermedie, dobbiamo approfondire e fare riferimento alla Capturesproprietà sul gruppo in questione:

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

inserisci qui la descrizione dell'immagine

Per gentile concessione di questo articolo .


3
Bell'articolo. Un'immagine vale più di mille parole.
AlexWei,

Sei una stella.
mikemay,

14

Dalla documentazione MSDN :

La vera utilità della proprietà Captures si verifica quando un quantificatore viene applicato a un gruppo di acquisizione in modo che il gruppo acquisisca più sottostringhe in una singola espressione regolare. In questo caso, l'oggetto Gruppo contiene informazioni sull'ultima sottostringa catturata, mentre la proprietà Captures contiene informazioni su tutte le sottostringhe catturate dal gruppo. Nel seguente esempio, l'espressione regolare \ b (\ w + \ s *) +. corrisponde a un'intera frase che termina in un punto. Il gruppo (\ w + \ s *) + acquisisce le singole parole nella raccolta. Poiché la raccolta Gruppo contiene informazioni solo sull'ultima sottostringa acquisita, acquisisce l'ultima parola nella frase "frase". Tuttavia, ogni parola acquisita dal gruppo è disponibile dalla raccolta restituita dalla proprietà Captures.


4

Immagina di avere il seguente input di testo dogcatcatcate un modello similedog(cat(catcat))

In questo caso, hai 3 gruppi, il primo ( gruppo principale ) corrisponde alla partita.

Match == dogcatcatcate Group0 ==dogcatcatcat

Gruppo1 == catcatcat

Gruppo2 == catcat

Quindi di cosa si tratta?

Consideriamo un piccolo esempio scritto in C # (.NET) usando la Regexclasse.

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

Uscita :

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

Analizziamo solo la prima corrispondenza ( match0).

Come si può vedere ci sono tre gruppi minori : group3, group4egroup5

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

Quei gruppi (3-5) sono stati creati a causa del ' sottostruttura ' (...)(...)(...)del modello principale (dog(cat(...)(...)(...)))

Il valore di group3corrisponde a è capture ( capture0). (Come nel caso di group4e group5). Questo perché non ci sono ripetizioni di gruppo come (...){3}.


Ok, consideriamo un altro esempio in cui c'è una ripetizione di gruppo .

Se modifichiamo il modello di espressione regolare da abbinare (per il codice mostrato sopra) da (dog(cat(...)(...)(...)))a (dog(cat(...){3})), si noterà che non v'è la seguente ripetizione gruppo : (...){3}.

Ora l' output è cambiato:

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

Ancora una volta, analizziamo solo la prima corrispondenza ( match0).

Non ci sono più gruppi minori group4 e a group5causa della (...){3} ripetizione ( {n} in cui n> = 2 ) sono stati uniti in un singolo gruppo group3.

In questo caso, il group3valore corrisponde a esso capture2( l'ultima acquisizione , in altre parole).

Quindi se avete bisogno di tutte le 3 catture interne ( capture0, capture1, capture2) si dovrà scorrere del gruppo Capturesdi raccolta.

La conclusione è: presta attenzione al modo in cui progetti i gruppi del tuo modello. Dovresti pensare in anticipo quale comportamento causa le specifiche del gruppo, come (...)(...), (...){2}o (.{3}){2}ecc.


Speriamo che aiuti a far luce sulle differenze tra Catture , Gruppi e Partite .

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.