В чем разница между "группами" и "перехватами" в регулярных выражениях .NET? - PullRequest
149 голосов
/ 23 июля 2010

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

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'?

Ответы [ 5 ]

117 голосов
/ 23 июля 2010

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

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

и далее:

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

И несколько страниц спустя, это его заключение:

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

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


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

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

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

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

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

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

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

17 голосов
/ 28 июня 2014

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

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

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

"(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] .Captures. Довольно бесполезно ..

14 голосов
/ 23 июля 2010

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

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

10 голосов
/ 16 июля 2017

Это можно объяснить на простом примере (и рисунках).

Соответствие 3:10pm с регулярным выражением ((\d)+):((\d)+)(am|pm) и использование интерактивного моно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? enter image description here

Так как в четвертой группе совпадают несколько цифрмы только "добираемся" до последнего совпадения, если мы ссылаемся на группу (то есть с неявным значением 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" }

enter image description here

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

4 голосов
/ 14 мая 2017

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

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

совпадение == dogcatcatcat и Group0 == dogcatcatcat

Group1 == catcatcat

Group2 == catcat

Так что же это такое?

Давайте рассмотрим небольшой пример, написанный на C # (.NET) с использованием Regex class.

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

Output :

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) были созданы из-за '1041 * подшаблона ' (...)(...)(...) основного шаблона (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} и т. Д.


Надеюсь, это поможет пролить свет на различия между Захваты , Группы и Совпадения .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...