Какая разница между "группами" и "захватывает" в регулярных выражениях .NET?

Я немного расплывчатый, что разница между "группой" и "захватом" - это когда речь идет о языке регулярных выражений .NET. Рассмотрим следующий код С#:

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

Я ожидаю, что это приведет к единственному захвату для буквы "Q", но если я напечатаю свойства возвращаемого MatchCollection, я вижу:

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

Что именно здесь происходит? Я понимаю, что там также есть захват на весь матч, но как поступают группы? И почему matches[0].Captures не включает захват для буквы "Q"?

Ответ 1

Ты не будешь первым, кто нечеткий об этом. Вот о чем говорит знаменитый Jeffrey Friedl (стр. 437 +):

В зависимости от вашего вида он добавляет интересное новое измерение сопоставить результаты или добавить путаницу и раздуваться.

И далее:

Основное различие между Группой объект и объект Capture - это то, что каждый объект группы содержит коллекция Captures, представляющая все промежуточные совпадения группы во время матча, а также окончательный текст, соответствующий группе.

И несколько страниц позже, это его вывод:

После прохождения .NET документации и фактически понимая, что добавляют эти объекты, У меня смешанные чувства к ним. На с одной стороны, это интересно инновации [..], с другой стороны, это кажется, добавляет бремя эффективности [..] функциональности, которая не будет использоваться в большинстве случаев

Другими словами: они очень похожи, но иногда и, как это бывает, вы найдете для них пользу. Прежде чем вырастить еще одну седую бороду, вы можете даже полюбить Captures...


Так как ни вышеприведенное, ни то, что говорится в другом сообщении, действительно не отвечает на ваш вопрос, рассмотрите следующее. Подумайте о том, что Captures как своего рода трекер истории. Когда регулярное выражение делает совпадение, оно проходит через строку слева направо (игнорируя обратное отслеживание на мгновение), и когда он встречает сопоставимые скобки для скобок, он будет хранить это в $x (x - любая цифра), пусть $1.

Нормальные двигатели регулярных выражений, когда скопирующие скобки должны быть повторены, выбрасывают текущий $1 и заменят его новым значением. Не .NET, который сохранит эту историю и разместит ее в Captures[0].

Если мы изменим ваше регулярное выражение, получим следующее:

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

вы заметите, что первый Group будет иметь один Captures (первая группа всегда будет полным совпадением, т.е. равна $0), а вторая группа будет удерживать {S}, т.е. только последняя соответствующая группа. Однако, и здесь catch, если вы хотите найти два других улова, они находятся в Captures, который содержит все промежуточные захваты для {Q} {R} и {S}.

Если вы когда-нибудь задумывались о том, как вы можете получить от множественного захвата, который показывает только последнее совпадение с отдельными записями, которые явно присутствуют в строке, вы должны использовать Captures.

Последнее слово на ваш последний вопрос: общее совпадение всегда имеет один общий захват, не смешивайте его с отдельными группами. Захваты интересны только внутри групп.

Ответ 2

Из документа MSDN :

Реальная полезность свойства Captures возникает, когда квантификатор применяется к группе захвата, так что группа захватывает несколько подстрок в одном регулярном выражении. В этом случае объект Group содержит информацию о последней захваченной подстроке, тогда как свойство Captures содержит информацию обо всех подстроках, захваченных группой. В следующем примере регулярное выражение \b (\ w +\s *)+. соответствует целому предложению, которое заканчивается за период. Группа (\ w +\s *) + захватывает отдельные слова в коллекции. Поскольку коллекция Group содержит информацию только о последней захваченной подстроке, она фиксирует последнее слово в предложении "предложение". Однако каждое слово, захваченное группой, доступно из коллекции, возвращенной свойством Captures.

Ответ 3

Группа - это то, что мы связывали с группами в регулярных выражениях

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

за исключением того, что это только "захваченные" группы. Не захватывающие группы (используя синтаксис '(?:' Здесь не представлены.

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

A Capture - это также то, что мы связали с "захваченными группами". Но когда группа применяется с квантификатором несколько раз, только последнее совпадение сохраняется как совпадение группы. В массиве захватов хранятся все эти соответствия.

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

Что касается вашего последнего вопроса - я бы подумал, прежде чем рассматривать это, что Captures будет массивом захватов, упорядоченных группой, к которой они принадлежат. Скорее это просто псевдоним для групп [0]. Захваты. Довольно бесполезно.

Ответ 4

Представьте, что у вас есть следующий ввод текста dogcatcatcat и шаблон, подобный dog(cat(catcat))

В этом случае у вас есть 3 группы, первая (основная группа) соответствует совпадению.

Match == dogcatcatcat и Group0 == dogcatcatcat

Группа1 == catcatcat

Группа2 == catcat

Итак, что это такое?

Рассмотрим небольшой пример, написанный на С# (.NET) с использованием класса 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();
        }

Выход

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

Проанализировать только первое совпадение (match0).

Как вы можете видеть, есть три младшие группы: group3, group4 и group5

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

Эти группы (3-5) были созданы из-за "подшаблона" (...)(...)(...) основного шаблона (dog(cat(...)(...)(...)))

Значение group3 соответствует захвату (capture0). (Как и в случае group4 и group5). Это потому, что нет повторения группы, например (...){3}.


Хорошо, рассмотрим другой пример, где есть повторение группы.

Если мы изменим шаблон регулярного выражения для соответствия (для кода, показанного выше) от (dog(cat(...)(...)(...))) до (dog(cat(...){3})), вы заметите, что существует следующее групповое повторение: (...){3}.

Теперь Вывод изменился:

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

Опять же, проанализируем только первое совпадение (match0).

Нет меньших групп group4 и group5 из-за (...){3} повторения ({n}, где n >= 2) они были объединены в одну группу group3.

В этом случае значение group3 соответствует ему capture2 (последний захват, другими словами).

Таким образом, если вам нужны все 3 внутренних захвата (capture0, capture1, capture2), вам придется циклически перемещаться по коллективу Captures.

Включение: обратите внимание на то, как вы создаете группы шаблонов. Вы должны подумать, какое поведение вызывает групповую спецификацию, например (...)(...), (...){2} или (.{3}){2} и т.д.


Надеюсь, это поможет пролить свет на различия между Captures, группами и совпадениями.

Ответ 5

Это можно объяснить простым примером (и изображениями).

Совместим 3:10pm с регулярным выражением ((\d)+):((\d)+)(am|pm) и с помощью 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" }

Итак, где 1? введите описание изображения здесь

Поскольку в четвертой группе есть несколько цифр, мы получаем только "получить" последнее совпадение, если мы ссылаемся на группу (с неявным ToString(), то есть). Чтобы разоблачить промежуточные совпадения, нам нужно глубже и ссылаться на свойство Captures в соответствующей группе:

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

введите описание изображения здесь

Предоставлено в этой статье.