Какая разница между "группами" и "захватывает" в регулярных выражениях .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" }
Предоставлено в этой статье.