Что такое группы балансировки регулярных выражений?



Я просто читал вопрос о том, как получить данные внутри двойных фигурных скобок (этот вопрос), а затем кто-то поднял балансировочные группы. Я все еще не совсем уверен, что это такое и как их использовать.



Я прочитал Определение Группы Балансируя, но объяснение трудно следовать, и я все еще довольно запутался в вопросах, которые я упомянул.



может ли кто-нибудь просто объяснить, что такое балансирующие группы и как они полезны?

593   2  

2 ответов:

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

В Сторону: Повторные Группы

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

чтобы проиллюстрировать это на примере, рассмотрим шаблон

(.)+

и строку "abcd".

во всех других регулярных выражениях вкусов, захват группы 1 просто даст один результат:d (обратите внимание, что полный матч, конечно, будет abcd как и ожидалось). Это происходит потому, что каждое новое использование группы захвата перезаписывает предыдущий захват.

.Чистая, с другой стороны, помнит их все. И он делает это в стеке. После сопоставления выше регулярное выражение, как

Match m = new Regex(@"(.)+").Match("abcd");

вы найдете, что

m.Groups[1].Captures

это CaptureCollection чьи элементы соответствуют четырем захватам

0: "a"
1: "b"
2: "c"
3: "d"

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

это становится более интересным, если мы используем именованные группы захвата. Поскольку .NET позволяет многократно использовать одно и то же имя, мы могли бы написать регулярное выражение типа

(?<word>\w+)\W+(?<word>\w+)

чтобы захватить два слова в одной группе. Опять же, каждый раз, когда группа с определенным именем обнаруженный захват помещается в его стек. Так что применяя это регулярное выражение к входу "foo bar" и осмотра

m.Groups["word"].Captures

мы находим два захватывает

0: "foo"
1: "bar"

это позволяет нам даже толкать вещи в один стек из разных частей выражения. Но все же, это просто особенность .NET в том, чтобы отслеживать несколько захватов, которые перечислены в этом CaptureCollection. Но я сказал, что эта коллекция является стек. Так что мы можем поп вещи от него?

Введите: Балансировка Групп

оказывается, мы можем. Если мы используем группу как (?<-word>...), то последний захват выскочил из стека word если подвыражения ... матчи. Поэтому, если мы изменим наше предыдущее выражение на

(?<word>\w+)\W+(?<-word>\w+)

тогда вторая группа выскочит захват первой группы, и мы получим пустой CaptureCollection в конце. Конечно, этот пример довольно бесполезен.

но есть еще одна деталь в минус-синтаксис: если стек уже пуст, группа не (независимо от шаблона). Мы можем использовать это поведение для подсчета уровней вложенности-и именно отсюда происходит группа балансировки имен (и где она становится интересной). Скажем, мы хотим сопоставить строки, которые правильно заключены в скобки. Мы нажимаем каждую открывающую скобку в стеке и открываем один захват для каждой закрывающей скобки. Если мы столкнемся с одной закрывающей скобкой слишком много, он попытается вытащить пустой стек и вызвать шаблон для отказа:

^(?:[^()]|(?<Open>[(])|(?<-Open>[)]))*$

таким образом, у нас есть три альтернативы в повторении. Первый вариант потребляет все, что не в скобках. Второй вариант соответствует (s при нажатии их на стек. Третий вариант соответствует )пока выталкивание элементов из стека (если это возможно!).

Примечание: просто чтобы уточнить, мы только проверяем, что нет непревзойденных скобок! Это означает, что строка, содержащая без скобок вообще.будет совпадают, потому что они все еще синтаксически допустимы (в некотором синтаксисе, где вам нужны ваши скобки, чтобы соответствовать). Если вы хотите обеспечить хотя бы один набор скобок, просто добавьте lookahead (?=.*[(]) сразу после ^.

этот шаблон не подходит (или совсем корректен).

Финал: Условные Шаблоны

есть еще одна загвоздка: это не гарантирует, что стек пустой конец строки (отсюда (foo(bar) будет действительным). .NET (и многие другие варианты) имеют еще одну конструкцию, которая помогает нам здесь: условные шаблоны. Общий синтаксис:

(?(condition)truePattern|falsePattern)

здесь falsePattern является необязательным - если он опущен, то false-case всегда будет совпадать. Условие может быть либо шаблоном, либо именем группы захвата. Я сосредоточусь на последнем случае здесь. Если это имя группы захвата, то truePattern используется тогда и только тогда, когда стек захвата для эта конкретная группа не пуста. То есть условный шаблон типа (?(name)yes|no) гласит: "если name сопоставил и захватил что-то (что все еще находится в стеке), используйте pattern yes в противном случае используйте шаблон no".

так что в конце нашего выше шаблона мы могли бы добавить что-то вроде (?(Open)failPattern) что приводит к сбою всего шаблона, если Open-стек не пуст. Самая простая вещь, чтобы сделать шаблон безоговорочно провал (?!) (пустой отрицательный взгляд). Итак, у нас есть наш окончательный шаблон:

^(?:[^()]|(?<Open>[(])|(?<-Open>[)]))*(?(Open)(?!))$

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

отсюда, небо это предел. Многие очень сложные применения возможны, и есть некоторые gotchas при использовании в сочетании с другими функциями .NET-Regex, такими как lookbehinds переменной длины (которому мне самому пришлось учиться на собственном горьком опыте). Однако главный вопрос всегда: ваш код по-прежнему поддерживается при использовании этих функций? Вам нужно документировать его очень хорошо, и убедитесь, что все, кто работает над ним, также знают об этих функциях. В противном случае вам может быть лучше, просто пройдя строку вручную по символам и подсчитав уровни вложенности в целое число.

добавление: что с (?<A-B>...) синтаксис?

кредиты для этой части идут к Коби (см. Его ответ ниже для получения более подробной информации).

теперь с все вышесказанное, мы можем проверить, что строка правильно заключена в скобки. Но было бы намного полезнее, если бы мы действительно могли получить (вложенные) захваты для всего содержимого этих скобок. Конечно, мы могли бы запомнить Открытие и закрытие скобок в отдельном стеке захвата, который не опустошен, а затем выполнить некоторое извлечение подстроки на основе их позиций в отдельном шаге.

но .NET предоставляет еще одну удобную функцию здесь: если мы используем (?<A-B>subPattern), это не только захват выскочил из стека B, но и все между тем выскочил захват B и эта текущая группа помещается в стек A. Поэтому, если мы используем такую группу для закрывающих скобок, при этом выталкивая уровни вложенности из нашего стека, мы также можем переместить содержимое пары в другой стек:

^(?:[^()]|(?<Open>[(])|(?<Content-Open>[)]))*(?(Open)(?!))$

Коби предусмотренный настоящим Live-Demo в ответ

Итак, взяв все эти вещи вместе мы может:

  • помните произвольно много захватов
  • проверка вложенных структур
  • захват каждого уровня вложенности

все в одном регулярном выражении. Если это не интересно... ;)

некоторые ресурсы, которые я нашел полезными, когда я впервые узнал о них:

просто небольшое дополнение к превосходному ответу М. Бюттнера:

в чем дело с (?<A-B>) синтаксис?

(?<A-B>x) - это тонко отличается от (?<-A>(?<B>x)). Они приводят к тому же потоку управления*, но захват по-разному.
Например, давайте рассмотрим шаблон для сбалансированных фигурных скобок:

(?:[^{}]|(?<B>{)|(?<-B>}))+(?(B)(?!))

в конце матча у нас есть сбалансированная строка, но это все, что у нас есть - мы не знаем здесь фигурные скобки, потому что B стек пуст. Тяжелая работа, которую двигатель сделал для нас, ушла.
( пример по регулярному выражению Storm)

(?<A-B>x) является решением этой проблемы. Как? Это не захват x на $A: он захватывает содержимое между предыдущим захватом B и текущее положение.

давайте использовать его в нашем шаблоне:

(?:[^{}]|(?<Open>{)|(?<Content-Open>}))+(?(Open)(?!))

это захват в $Content строки между фигурными скобками (и их позиции), для каждой пары по пути.
Для строки {1 2 {3} {4 5 {6}} 7} было бы четыре захвата:3,6,4 5 {6} и 1 2 {3} {4 5 {6}} 7 - гораздо лучше, чем ничего или }}}}.
(пример - щелкните table tab и посмотрите на ${Content} отражает)

на самом деле, его можно использовать без балансировать на всех: (?<A>).(.(?<Content-A>).) захватывает первые два символа, даже если они разделены группами.
(здесь чаще используется lookahead, но он не всегда масштабируется: он может дублировать вашу логику.)

(?<A-B>) является сильной особенностью-это дает вам точно контроль над вашими захватами. Имейте это в виду, когда вы пытаетесь получить больше от вашего рисунка.

Comments

    Ничего не найдено.