Qual é a diferença entre "grupos" e "capturas" nas expressões regulares do .NET?

161

Estou um pouco confuso sobre qual é a diferença entre um "grupo" e uma "captura" quando se trata da linguagem de expressão regular do .NET. Considere o seguinte código C #:

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

Espero que isso resulte em uma única captura para a letra 'Q', mas se eu imprimir as propriedades do retornado MatchCollection, vejo:

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

O que exatamente está acontecendo aqui? Entendo que também há uma captura para o jogo inteiro, mas como os grupos entram? E por que não matches[0].Capturesinclui a captura da letra 'Q'?

Nick Meyer
fonte

Respostas:

126

Você não será o primeiro a ficar confuso com isso. Aqui está o que o famoso Jeffrey Friedl tem a dizer sobre isso (páginas 437 ou mais):

Dependendo da sua visão, ele adiciona uma nova dimensão interessante aos resultados da partida ou confusão e inchaço.

E mais adiante:

A principal diferença entre um objeto de grupo e um objeto de captura é que cada objeto de grupo contém uma coleção de capturas representando todas as correspondências intermediárias pelo grupo durante a correspondência, bem como o texto final correspondente ao grupo.

E algumas páginas depois, esta é sua conclusão:

Depois de passar pela documentação do .NET e realmente entender o que esses objetos adicionam, tenho sentimentos contraditórios sobre eles. Por um lado, é uma inovação interessante [...] por outro lado, parece acrescentar um ônus de eficiência [...] a uma funcionalidade que não será usada na maioria dos casos

Em outras palavras: eles são muito semelhantes, mas ocasionalmente e, por acaso, você encontrará um uso para eles. Antes de cultivar outra barba grisalha, você pode até gostar das Capturas ...


Como nem o exposto acima, nem o que foi dito na outra postagem realmente parece responder à sua pergunta, considere o seguinte. Pense no Capture como um tipo de rastreador de história. Quando o regex faz a correspondência, ele passa pela string da esquerda para a direita (ignorando o retorno por um momento) e quando encontra parênteses de captura correspondentes, o armazena em $x(x sendo qualquer dígito), digamos $1.

Os mecanismos regex normais, quando os parênteses de captura devem ser repetidos, jogam fora a corrente $1e a substituem pelo novo valor. Não é o .NET, que manterá esse histórico e o incluirá Captures[0].

Se alterarmos sua regex para a seguinte aparência:

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

você notará que o primeiro Groupterá um Captures(o primeiro grupo sempre sendo o jogo inteiro, ou seja, igual a $0) e o segundo grupo será válido {S}, ou seja, apenas o último grupo correspondente. No entanto, e aqui está o problema, se você quiser encontrar os outros dois, eles estão Captures, que contém todas as capturas intermediárias para {Q} {R}e {S}.

Se você já se perguntou como poderia obter a captura múltipla, que mostra apenas a última correspondência com as capturas individuais que estão claramente lá na sequência, você deve usar Captures.

Uma palavra final na sua pergunta final: a correspondência total sempre tem uma captura total, não a misture com os grupos individuais. As capturas são interessantes apenas dentro de grupos .

Abel
fonte
1
a functionality that won't be used in the majority of casesEu acho que ele perdeu o barco. No curto prazo, (?:.*?(collection info)){4,20}aumenta a eficiência em mais do que algumas centenas por cento.
1
@ SLN, não sei ao que você está se referindo e quem 'ele' é (friedl?). O exemplo que você dá parece não ter relação com essa discussão ou com as expressões usadas. Além disso, quantificadores não gananciosos raramente são mais eficientes que quantificadores gananciosos e requerem conhecimento do conjunto de entradas e testes cuidadosos de desempenho.
Abel
@ Abel - cheguei aqui de uma pergunta marcada duplicada disso. Eu vejo Friedl citado. Esta postagem é antiga e precisa ser atualizada para mantê-la moderna. Somente com o Dot Net isso pode ser feito, é o que o separa da maioria dos outros. Discriminação: um exemplo geral quantificado de grupo não capturado (?:..)+. Corresponda preguiçosamente qualquer coisa .*?até uma subexpressão de captura (grupo). Continue. Em uma única partida, uma coleção de grupos precipita uma matriz do que é necessário. Não há necessidade de encontrar o próximo, não há reentrada tornando-o 10 a 20 ou mais vezes mais rápido.
1
@ SLN, esta pergunta é sobre outra coisa e é especificamente sobre um recurso .net não encontrado em outros mecanismos de regex (grupos vs capturas, consulte o título). Não vejo nada desatualizado aqui, .net ainda está funcionando da mesma maneira, na verdade, essa parte não mudou há muito tempo no .net. O desempenho não faz parte da questão. Sim, o agrupamento sem captura é mais rápido, mas, novamente, o assunto aqui é o oposto. Por que o ganancioso é mais rápido que o preguiçoso é explicado em muitos textos on-line e no livro de friedl, mas aqui no AT. Talvez a outra pergunta (qual?) Não fosse uma duplicata verdadeira?
Abel
2
@ Abel - Eu sei que continuo dizendo isso, mas você não ouve. Eu me ofendo com essa declaração de Friedl a functionality that won't be used in the majority of cases. De fato, é a funcionalidade mais procurada em regiões regex. Preguiçoso / ganancioso? O que isso tem a ver com meus comentários? Permite ter uma quantidade variável de buffers de captura. Ele pode varrer toda a cadeia em uma única correspondência. Se .*?(dog)encontrar o primeiro dog, (?:.*?(dog))+encontrará tudo dog em toda a sequência em uma única correspondência. O aumento de desempenho é perceptível.
20

Um grupo é o que associamos a grupos em expressões regulares

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

exceto que esses são apenas grupos 'capturados'. Grupos que não capturam (usando a sintaxe '(?:') Não são representados aqui.

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

Uma captura também é o que associamos aos 'grupos capturados'. Porém, quando o grupo é aplicado com um quantificador várias vezes, apenas a última correspondência é mantida como a correspondência do grupo. A matriz de capturas armazena todas essas correspondências.

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

Quanto à sua última pergunta - eu teria pensado antes de examinar isso que o Capture seria um conjunto de capturas ordenadas pelo grupo ao qual pertencem. Pelo contrário, é apenas um apelido para os grupos [0]. Muito inútil ..

Gerard ONeill
fonte
Explicação clara (y)
Ghasan 6/03/19
19

Isso pode ser explicado com um exemplo simples (e imagens).

Correspondendo 3:10pmà expressão regular ((\d)+):((\d)+)(am|pm)e usando o 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" }

Então, onde está o 1? insira a descrição da imagem aqui

Como existem vários dígitos correspondentes no quarto grupo, apenas "chegamos" à última correspondência se fizermos referência ao grupo (com um implícito ToString()). Para expor as correspondências intermediárias, precisamos nos aprofundar e fazer referência à Capturespropriedade no grupo em questão:

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

insira a descrição da imagem aqui

Cortesia deste artigo .

Eric Smith
fonte
3
Artigo agradável. Uma imagem vale mais que mil palavras.
AlexWei
Você é uma estrela.
mikemay 23/06
14

Na documentação do MSDN :

A utilidade real da propriedade Captures ocorre quando um quantificador é aplicado a um grupo de captura para que o grupo capture várias substrings em uma única expressão regular. Nesse caso, o objeto Group contém informações sobre a última substring capturada, enquanto a propriedade Captures contém informações sobre todas as substrings capturadas pelo grupo. No exemplo a seguir, a expressão regular \ b (\ w + \ s *) +. corresponde a uma frase inteira que termina em um período. O grupo (\ w + \ s *) + captura as palavras individuais na coleção. Como a coleção Group contém informações apenas sobre a última substring capturada, ela captura a última palavra na frase "sentença". No entanto, cada palavra capturada pelo grupo está disponível na coleção retornada pela propriedade Captures.

pmarflee
fonte
4

Imagine que você tenha a seguinte entrada de texto dogcatcatcate um padrão comodog(cat(catcat))

Nesse caso, você tem 3 grupos, o primeiro ( grupo principal ) corresponde à partida.

Corresponder == dogcatcatcate Grupo0 ==dogcatcatcat

Grupo1 == catcatcat

Grupo2 == catcat

Então, o que é isso tudo?

Vamos considerar um pequeno exemplo escrito em C # (.NET) usando a 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();
        }

Saída :

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

Vamos analisar apenas a primeira correspondência ( match0).

Como você pode ver, existem três grupos menores : group3, group4egroup5

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

Esses grupos (3-5) foram criados devido ao ' sub-padrão ' (...)(...)(...)do padrão principal (dog(cat(...)(...)(...)))

O valor de group3corresponde à sua captura ( capture0). (Como no caso de group4e group5). Isso ocorre porque não há repetição de grupo como (...){3}.


Ok, vamos considerar outro exemplo em que há uma repetição em grupo .

Se modificar o padrão de expressão regular a ser correspondido (para código mostrado acima) de (dog(cat(...)(...)(...)))que (dog(cat(...){3})), você notará que há a seguinte repetição grupo : (...){3}.

Agora a saída mudou:

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

Novamente, vamos analisar apenas a primeira correspondência ( match0).

Não há mais grupos menores group4 e, group5devido à (...){3} repetição ( {n} em que n> = 2 ), eles foram mesclados em um único grupo group3.

Nesse caso, o group3valor corresponde a ele capture2( a última captura , em outras palavras).

Assim, se você precisa de todos os 3 captações internas ( capture0, capture1, capture2) você terá que percorrer do grupo Capturescoleção.

A conclusão é: preste atenção na maneira como você cria os grupos de seus padrões. Você deve pensar antecipadamente o comportamento faz com que a especificação do grupo, como (...)(...), (...){2}ou (.{3}){2}etc.


Espero que ajude a esclarecer as diferenças entre Capturas , Grupos e Jogos também.

AlexMelw
fonte