Quelle est la différence entre les «groupes» et les «captures» dans les expressions régulières .NET?

162

Je suis un peu flou sur la différence entre un "groupe" et une "capture" en ce qui concerne le langage d'expression régulière de .NET. Considérez le code C # suivant:

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

Je m'attends à ce que cela entraîne une capture unique pour la lettre `` Q '', mais si j'imprime les propriétés du renvoyé MatchCollection, je vois:

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

Que se passe-t-il exactement ici? Je comprends qu'il y a aussi une capture pour tout le match, mais comment les groupes entrent-ils? Et pourquoi n'inclut pas matches[0].Capturesla capture de la lettre «Q»?

Nick Meyer
la source

Réponses:

126

Vous ne serez pas le premier à être flou à ce sujet. Voici ce que le célèbre Jeffrey Friedl en dit (pages 437+):

Selon votre point de vue, cela ajoute une nouvelle dimension intéressante aux résultats du match, ou ajoute de la confusion et du gonflement.

Et plus loin:

La principale différence entre un objet Group et un objet Capture est que chaque objet Group contient une collection de Captures représentant toutes les correspondances intermédiaires par le groupe pendant la correspondance, ainsi que le texte final mis en correspondance par le groupe.

Et quelques pages plus tard, voici sa conclusion:

Après avoir dépassé la documentation .NET et compris ce que ces objets ajoutent, j'ai des sentiments mitigés à leur sujet. D'une part, c'est une innovation intéressante [..] d'autre part, elle semble ajouter une charge d'efficacité [..] d'une fonctionnalité qui ne sera pas utilisée dans la majorité des cas

En d'autres termes: ils sont très similaires, mais parfois et au fur et à mesure, vous leur trouverez une utilité. Avant de faire pousser une autre barbe grise, vous pouvez même vous adonner aux Captures ...


Étant donné que ni ce qui précède, ni ce qui est dit dans l'autre article ne semble vraiment répondre à votre question, considérez ce qui suit. Considérez Captures comme une sorte de suivi de l'historique. Lorsque l'expression régulière fait sa correspondance, elle parcourt la chaîne de gauche à droite (en ignorant le retour arrière pendant un moment) et lorsqu'elle rencontre une parenthèse de capture correspondante, elle la stockera dans $x(x étant n'importe quel chiffre), disons $1.

Les moteurs de regex normaux, lorsque les parenthèses de capture doivent être répétées, jetteront le courant $1et le remplaceront par la nouvelle valeur. Pas .NET, qui conservera cette histoire et la placera dans Captures[0].

Si nous changeons votre expression régulière pour qu'elle ressemble à ceci:

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

vous remarquerez que le premier en Groupaura un Captures(le premier groupe étant toujours le match complet, c'est-à-dire égal à $0) et le second groupe tiendra {S}, c'est-à-dire seulement le dernier groupe correspondant. Cependant, et voici la capture, si vous voulez trouver les deux autres captures, elles sont dans Captures, qui contient toutes les captures intermédiaires pour {Q} {R}et {S}.

Si vous vous êtes déjà demandé comment vous pourriez obtenir de la capture multiple, qui ne montre que la dernière correspondance pour les captures individuelles qui sont clairement là dans la chaîne, vous devez utiliser Captures.

Un dernier mot sur votre dernière question: le match total a toujours une capture totale, ne mélangez pas cela avec les groupes individuels. Les captures ne sont intéressantes qu'à l'intérieur des groupes .

Abel
la source
1
a functionality that won't be used in the majority of casesJe pense qu'il a raté le bateau. À court terme, (?:.*?(collection info)){4,20}augmente l'efficacité de plus de quelques centaines de pour cent.
1
@sln, je ne sais pas à quoi vous faites référence et à qui «il» est (friedl?). L'exemple que vous donnez ne semble pas lié à cette discussion, ni aux expressions utilisées. En outre, les quantificateurs non gourmands ne sont que très rarement plus efficaces que les quantificateurs gourmands, et nécessitent une connaissance de l'ensemble d'entrée et des tests de performance minutieux.
Abel
@Abel - J'ai atterri ici à partir d'une question marquée en double. Je vois Friedl cité. Ce message est ancien et doit être actualisé pour le garder moderne. Ce n'est qu'avec Dot Net que cela peut être fait, c'est ce qui le distingue de la plupart des autres. Ventilation: un exemple de groupe global non capturé quantifié (?:..)+. Faites correspondre paresseusement n'importe quoi .*?jusqu'à une sous-expression de capture (groupe). Continuez. Dans une seule correspondance, une collection de groupe précipite un tableau de ce qui est nécessaire. Il n'y a pas besoin de trouver ensuite, il n'y a pas de rentrée, ce qui le rend 10 à 20 fois ou plus plus rapide.
1
@sln, cette question concerne autre chose et elle concerne spécifiquement une fonctionnalité .net introuvable dans d'autres moteurs d'expression régulière (groupes vs captures, voir le titre). Je ne vois rien de désuet ici, .net fonctionne toujours de la même manière, en fait cette partie n'a pas changé depuis longtemps dans .net. La performance ne fait pas partie de la question. Oui, le regroupement sans capture est plus rapide, mais encore une fois, le sujet ici est le contraire. Pourquoi gourmand est plus rapide que paresseux est expliqué dans de nombreux textes en ligne et dans le livre de friedl, mais OT ici. Peut-être que l'autre question (laquelle?) N'était pas un vrai double?
Abel
2
@Abel - Je sais que je continue à le dire, mais vous ne l'entendez pas. Je prends ombrage cette déclaration de Friedl a functionality that won't be used in the majority of cases. En fait, c'est la fonctionnalité la plus recherchée dans le monde des regex. Paresseux / gourmand? Qu'est-ce que cela a à voir avec mes commentaires? Il permet d'avoir une quantité variable de tampons de capture. Il peut balayer toute la chaîne en une seule correspondance. Si .*?(dog)trouve le premier, dogalors (?:.*?(dog))+trouvera tout dog dans la chaîne entière en une seule correspondance. L'augmentation des performances est perceptible.
20

Un groupe est ce que nous avons associé aux groupes dans les expressions régulières

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

sauf que ce ne sont que des groupes «capturés». Les groupes non capturants (utilisant la syntaxe '(?:' Ne sont pas représentés ici.

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

Une capture est également ce que nous avons associé aux «groupes capturés». Mais lorsque le groupe est appliqué avec un quantificateur plusieurs fois, seule la dernière correspondance est conservée comme correspondance du groupe. Le tableau de captures stocke toutes ces correspondances.

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

En ce qui concerne votre dernière question - j'aurais pensé avant d'examiner cela que les captures seraient un tableau des captures ordonnées par le groupe auquel elles appartiennent. Il s'agit plutôt d'un alias des groupes [0] .Captures. Assez inutile.

Gérard ONeill
la source
Explication claire (y)
Ghasan
19

Cela peut être expliqué par un exemple simple (et des images).

Correspondance 3:10pmavec l'expression régulière ((\d)+):((\d)+)(am|pm)et utilisation de 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" }

Alors, où est le 1? entrez la description de l'image ici

Puisqu'il y a plusieurs chiffres qui correspondent sur le quatrième groupe, nous n'obtenons la dernière correspondance que si nous référençons le groupe (avec un implicite ToString(), c'est-à-dire). Afin d'exposer les correspondances intermédiaires, nous devons aller plus loin et référencer la Capturespropriété sur le groupe en question:

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

entrez la description de l'image ici

Avec l'aimable autorisation de cet article .

Eric Smith
la source
3
Bel article. Une image vaut mieux que mille mots.
AlexWei du
Tu es une étoile.
mikemay
14

À partir de la documentation MSDN :

La véritable utilité de la propriété Captures se produit lorsqu'un quantificateur est appliqué à un groupe de capture afin que le groupe capture plusieurs sous-chaînes dans une seule expression régulière. Dans ce cas, l'objet Group contient des informations sur la dernière sous-chaîne capturée, tandis que la propriété Captures contient des informations sur toutes les sous-chaînes capturées par le groupe. Dans l'exemple suivant, l'expression régulière \ b (\ w + \ s *) +. correspond à une phrase entière qui se termine par un point. Le groupe (\ w + \ s *) + capture les mots individuels de la collection. Étant donné que la collection Group contient des informations uniquement sur la dernière sous-chaîne capturée, elle capture le dernier mot de la phrase, «phrase». Cependant, chaque mot capturé par le groupe est disponible à partir de la collection renvoyée par la propriété Captures.

pmarflee
la source
4

Imaginez que vous ayez la saisie de texte suivante dogcatcatcatet un modèle commedog(cat(catcat))

Dans ce cas, vous avez 3 groupes, le premier ( grand groupe ) correspond au match.

Match == dogcatcatcatet Group0 ==dogcatcatcat

Groupe1 == catcatcat

Groupe2 == catcat

Alors de quoi s'agit-il?

Prenons un petit exemple écrit en C # (.NET) en utilisant 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();
        }

Sortie :

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

Analysons juste la première correspondance ( match0).

Comme vous pouvez le voir , il y a trois groupes mineurs : group3, group4etgroup5

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

Ces groupes (3-5) ont été créés en raison du `` sous- modèle '' (...)(...)(...)du modèle principal (dog(cat(...)(...)(...)))

La valeur de group3correspond à sa capture ( capture0). (Comme dans le cas de group4et group5). C'est parce qu'il n'y a pas de répétition de groupe comme (...){3}.


Ok, considérons un autre exemple où il y a une répétition de groupe .

Si nous modifions le modèle d'expression régulière pour être adapté (pour le code ci - dessus) de (dog(cat(...)(...)(...)))à (dog(cat(...){3})), vous remarquerez qu'il ya les éléments suivants répétition du groupe : (...){3}.

Maintenant, la sortie a changé:

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

Encore une fois, analysons uniquement la première correspondance ( match0).

Il n'y a plus de groupes mineurs group4 et à group5cause de la (...){3} répétition ( {n}n> = 2 ) ils ont été fusionnés en un seul groupe group3.

Dans ce cas, la group3valeur correspond à elle capture2( la dernière capture , en d'autres termes).

Ainsi , si vous avez besoin tous les 3 captures intérieures ( capture0, capture1, capture2) vous devrez faire défiler du groupe Capturesde la collection.

La conclusion est la suivante: faites attention à la façon dont vous concevez les groupes de votre modèle. Vous devriez penser dès le départ ce qui cause le comportement des spécifications du groupe, comme (...)(...), (...){2}ou (.{3}){2}etc.


Espérons que cela aidera également à faire la lumière sur les différences entre les captures , les groupes et les matchs .

AlexMelw
la source