web-dev-qa-db-fra.com

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

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 se traduise par une capture unique pour la lettre "Q", mais si j'imprime les propriétés du MatchCollection retourné, 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 matches[0].Captures inclure la capture de la lettre 'Q'?

149
Nick Meyer

Vous ne serez pas le premier à être flou à ce sujet. Voici ce qu'en dit le célèbre Jeffrey Friedl (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 des ballonnements.

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 correspondant au groupe.

Et quelques pages plus tard, voici sa conclusion:

Après avoir dépassé la documentation .NET et réellement 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, il semble ajouter un fardeau 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 de temps en temps et en l'occurrence, vous en trouverez une utilisation. Avant de faire pousser une autre barbe grise, vous pouvez même aimer les Captures ...


Étant donné que ni ce qui précède, ni ce qui est dit dans l'autre post ne semble vraiment répondre à votre question, considérez ce qui suit. Considérez les captures comme une sorte de traqueur d'histoire. 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 d'expression régulière normaux, lorsque les parenthèses de capture doivent être répétées, jetteront le courant $1 et le remplacera par la nouvelle valeur. Pas .NET, qui conservera cet historique et le placera dans Captures[0].

Si nous changeons votre expression régulière pour qu'elle se présente comme suit:

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

vous remarquerez que le premier Group aura un Captures (le premier groupe étant toujours la correspondance entière, c'est-à-dire, égal à $0) et le deuxième groupe tiendra {S}, c'est-à-dire uniquement 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 obtenir la capture multiple, qui n'affiche que la dernière correspondance avec les captures individuelles clairement présentes dans la chaîne, vous devez utiliser Captures.

Un dernier mot sur votre dernière question: la correspondance totale 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 .

117
Abel

Un groupe est ce que nous avons associé à des 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é plusieurs fois avec un quantificateur, 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 "

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

18
Gerard ONeill

Depuis le MSDN documentation :

L'utilité réelle 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 uniquement des informations 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 retournée par la propriété Captures.

14
pmarflee

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

Correspondance 3:10pm avec l'expression régulière ((\d)+):((\d)+)(am|pm), et en utilisant 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? enter image description here

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 une ToString() implicite, c'est-à-dire). Afin d'exposer les correspondances intermédiaires, nous devons aller plus loin et référencer la propriété Captures 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" }

enter image description here

Gracieuseté de cet article .

10
Eric Smith

Imaginez que vous ayez la saisie de texte suivante dogcatcatcat et un modèle comme dog(cat(catcat))

Dans ce cas, vous avez 3 groupes, le premier ( grand groupe ) correspond à la correspondance.

Faire correspondre == dogcatcatcat et Group0 == dogcatcatcat

Groupe1 == catcatcat

Groupe2 == catcat

Alors de quoi s'agit-il?

Prenons un petit exemple écrit en C # (.NET) en utilisant la classe Regex.

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 existe trois groupes mineurs : group3, group4 et group5

    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 motif principal (dog(cat(...)(...)(...)))

Valeur de group3 correspond à sa capture (capture0). (Comme dans le cas de group4 et group5). C'est parce que il n'y a pas de répétition de groupe comme (...){3}.


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

Si nous modifions le modèle d'expression régulière à mettre en correspondance (pour le code ci-dessus) à partir de (dog(cat(...)(...)(...))) à (dog(cat(...){3})), vous remarquerez qu'il y a ce qui suit répétition de groupe: (...){3}.

Maintenant, le 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 group5 à cause de (...){3}répétition ( {n} n> = 2 ) ils ont été fusionnés en un seul groupe group3.

Dans ce cas, le group3 la valeur correspond à son capture2 ( la dernière capture , en d'autres termes).

Ainsi, si vous avez besoin des 3 captures internes (capture0, capture1, capture2), vous devrez parcourir la collection Captures du groupe.

La conclusion est: faites attention à la façon dont vous concevez les groupes de votre modèle. Vous devriez penser à l'avance quel comportement provoque la spécification du groupe, comme (...)(...), (...){2} ou (.{3}){2} etc.


J'espère que cela aidera à faire la lumière sur les différences entre Captures, Groupes et Correspondances également.

4
AndreyWD