В чем разница между «группами» и «перехватами» в регулярных выражениях .NET?

162

Я немного размышляю о разнице между «группой» и «захватом», когда дело касается языка регулярных выражений .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»?

Ник Мейер
источник

Ответы:

126

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

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

И далее:

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

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

После ознакомления с документацией .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.

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

Абель
источник
1
a functionality that won't be used in the majority of casesЯ думаю, что он пропустил лодку. В краткосрочной перспективе (?:.*?(collection info)){4,20}эффективность увеличивается более чем на несколько сотен процентов.
1
@ sln, не уверен, что ты имеешь в виду и кто он (фридл?). Пример, который вы приводите, не имеет отношения к этому обсуждению или используемым выражениям. Кроме того, не жадные квантификаторы лишь очень редко более эффективны, чем жадные квантификаторы, и требуют знания входного набора и тщательного тестирования производительности.
Авель
@Abel - я приземлился здесь с вопросом, помеченным как дубликат этого. Я вижу цитату Фридла. Этот пост старый и нуждается в обновлении, чтобы он был современным. Это можно сделать только с помощью Dot Net, это то, что отличает его от большинства других. Разбивка: количественный пример группы без захвата (?:..)+. Ленивое сопоставление чего-либо .*?до подвыражения захвата (группы). Продолжайте дальше. В пределах одного совпадения групповая коллекция выделяет массив только того, что нужно. Нет необходимости искать дальше, нет повторного входа, что делает его в 10-20 и более раз быстрее.
1
@sln, этот вопрос касается чего-то другого, а именно о функции .net, не встречающейся в других движках регулярных выражений (группы и записи, см. заголовок). Я не вижу здесь ничего устаревшего, .net по-прежнему работает так же, фактически эта часть не изменилась в течение долгого времени в .net. Производительность не является частью вопроса. Да, группировка без захвата происходит быстрее, но опять же, тема здесь противоположна. Почему жадность быстрее ленивых объясняется во многих текстах онлайн и в книге Фридля, но ОТ здесь. Может быть, другой вопрос (который?) Не был истинным дубликатом?
Авель
2
@Abel - Я знаю, что продолжаю говорить это, но ты не слышишь это. Я обожаю это заявление Фридля a functionality that won't be used in the majority of cases. На самом деле это самая востребованная функциональность в регулярных выражениях. Ленивый / жадный? Какое это имеет отношение к моим комментариям? Это позволяет иметь переменное количество буферов захвата. Он может подмести всю строку в одном матче. Если .*?(dog)находит первое, dogто (?:.*?(dog))+найдет все dog во всей строке в одном совпадении. Увеличение производительности заметно.
20

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

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

Жерар ОНЕЙЛ
источник
Четкое объяснение (у)
Гасан
19

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

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

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

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

Эрик Смит
источник
3
Хорошая статья. Одна картинка стоит тысячи слов.
AlexWei
Ты звезда.
mikemay
14

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

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

pmarflee
источник
4

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

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

Match == dogcatcatcatи Group0 ==dogcatcatcat

Group1 == catcatcat

Group2 == catcat

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

Давайте рассмотрим небольшой пример, написанный на C # (.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}т. Д.


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

AlexMelw
источник