Захват квантификаторов и квантификатор арифметики
Прежде всего позвольте мне объяснить, что этот вопрос не касается ни того, как собирать группы, ни того, как использовать квантификаторы, двух функций регулярного выражения, с которыми я прекрасно знаком. Это более сложный вопрос для любителей регулярных выражений, которые могут быть знакомы с необычным синтаксисом в экзотических движках.
Захват квантификаторов
Кто-нибудь знает, позволяет ли аромат регулярных выражений захватывать квантификаторы? Под этим я подразумеваю, что будет подсчитано количество символов, совпадающих с квантификаторами, такими как + и *, и что это число можно будет снова использовать в другом квантификаторе.
Например, предположим, что вы хотели убедиться, что у вас есть одинаковое количество Ls и Rs в строке этого вида: LLLRRRRR
Вы можете представить себе такой синтаксис, как
L(+)R{\q1}
где квантификатор + для L захвачен, и где захваченное число упоминается в квантификаторе для R как {\q1}
Это было бы полезно, чтобы сбалансировать количество {@,=,-,/} в таких строках, как @@@@ "Star Wars" ==== "1977" ---- "Научная фантастика" //// " Джордж Лукас "
Отношение к рекурсии
В некоторых случаях захват квантификатора элегантно заменяет рекурсию, например фрагмент текста, обрамленный таким же количеством букв L и R,
L(+) some_content R{\q1}
Идея представлена в некоторых деталях на следующей странице: Захваченные квантификаторы
Здесь также обсуждается естественное расширение для захваченных квантификаторов: арифметика квантификаторов, для случаев, когда вы хотите сопоставить (3*x + 1) количество символов, сопоставленных ранее.
Я пытаюсь выяснить, существует ли что-то подобное.
Заранее спасибо за ваши идеи!!!
Обновить
Казимир дал фантастический ответ, который показывает два метода для проверки того, что различные части шаблона имеют одинаковую длину. Тем не менее, я не хотел бы полагаться на любой из них для повседневной работы. Это действительно трюки, которые демонстрируют отличное зрелищность. На мой взгляд, эти красивые, но сложные методы подтверждают предпосылку вопроса: функция регулярного выражения для захвата количества символов, которые могут быть сопоставлены квантификаторами (такими как + или *), сделала бы такие шаблоны балансировки очень простыми и расширила синтаксис в приятно выразительный способ.
Обновление 2 (намного позже)
Я обнаружил, что.NET имеет функцию, которая приближается к тому, о чем я спрашивал. Добавлен ответ, чтобы продемонстрировать функцию.
2 ответа
Я не знаю движок регулярных выражений, который может захватывать квантификатор. Тем не менее, с помощью PCRE или Perl можно использовать некоторые приемы, чтобы проверить, есть ли у вас одинаковое количество символов. С вашим примером:
@@@@ "Звездные войны" ==== "1977" ---- "Научная фантастика" //// "Джордж Лукас"
Вы можете проверить, если
@
=
-
/
сбалансированы с этим шаблоном, который использует знаменитый трюк Qtax, (вы готовы?): "притяжательно-опциональная группа самоссылки "~(?<!@)((?:@(?=[^=]*(\2?+=)[^-]*(\3?+-)[^/]*(\4?+/)))+)(?!@)(?=[^=]*\2(?!=)[^-]*\3(?!-)[^/]*\4(?!/))~
детали шаблона:
~ # pattern delimiter
(?<!@) # negative lookbehind used as an @ boundary
( # first capturing group for the @
(?:
@ # one @
(?= # checks that each @ is followed by the same number
# of = - /
[^=]* # all that is not an =
(\2?+=) # The possessive optional self-referencing group:
# capture group 2: backreference to itself + one =
[^-]*(\3?+-) # the same for -
[^/]*(\4?+/) # the same for /
) # close the lookahead
)+ # close the non-capturing group and repeat
) # close the first capturing group
(?!@) # negative lookahead used as an @ boundary too.
# this checks the boundaries for all groups
(?=[^=]*\2(?!=)[^-]*\3(?!-)[^/]*\4(?!/))
~
Главная идея
Группа без захвата содержит только одну @
, Каждый раз, когда эта группа повторяется, новый персонаж добавляется в группы захвата 2, 3 и 4.
притяжательно-опциональная группа, ссылающаяся на себя
Как это работает?
( (?: @ (?= [^=]* (\2?+ = ) .....) )+ )
При первом появлении символа @ группа захвата 2 еще не определена, поэтому вы не можете написать что-то подобное (\2 =)
это сделает шаблон неудачным. Чтобы избежать проблемы, способ сделать обратную ссылку необязательной: \2?
Второй аспект этой группы заключается в том, что количество символов =
matched увеличивается при каждом повторении группы без захвата, так как =
добавляется каждый раз. Чтобы гарантировать, что это число всегда увеличивается (или шаблон не выполняется), притяжательный квантификатор заставляет обратную ссылку сначала сопоставляться перед добавлением нового =
персонаж.
Обратите внимание, что эту группу можно увидеть так: если группа 2 существует, сопоставьте ее со следующей =
( (?(2)\2) = )
Рекурсивный путь
~(?<!@)(?=(@(?>[^@=]+|(?-1))*=)(?!=))(?=(@(?>[^@-]+|(?-1))*-)(?!-))(?=(@(?>[^@/]+|(?-1))*/)(?!/))~
Вам нужно использовать перекрывающиеся совпадения, так как вы будете использовать @ часть несколько раз, это причина того, что весь шаблон находится внутри обходных путей.
детали шаблона:
(?<!@) # left @ boundary
(?= # open a lookahead (to allow overlapped matches)
( # open a capturing group
@
(?> # open an atomic group
[^@=]+ # all that is not an @ or an =, one or more times
| # OR
(?-1) # recursion: the last defined capturing group (the current here)
)* # repeat zero or more the atomic group
= #
) # close the capture group
(?!=) # checks the = boundary
) # close the lookahead
(?=(@(?>[^@-]+|(?-1))*-)(?!-)) # the same for -
(?=(@(?>[^@/]+|(?-1))*/)(?!/)) # the same for /
Основное отличие от прецедентного паттерна состоит в том, что этот не заботится о порядке =
-
а также /
групп. (Однако вы можете легко внести некоторые изменения в первый шаблон, чтобы справиться с этим, с классами символов и негативными взглядами.)
Примечание: для примера строки, чтобы быть более точным, вы можете заменить отрицательный вид сзади на якорь (^
или же \A
). И если вы хотите получить всю строку как результат совпадения, вы должны добавить .*
в конце (в противном случае результат матча будет пустым, как это заметит игривый).
Возвращаясь через пять недель, потому что я узнал, что в.NET есть кое-что, что очень близко подходит к идее "захвата кванторов", упомянутой в вопросе. Функция называется "балансировочные группы".
Вот решение, которое я придумал. Это выглядит долго, но это довольно просто.
(?:@(?<c1>)(?<c2>)(?<c3>))+[^@=]+(?<-c1>=)+[^=-]+(?<-c2>-)+[^-/]+(?<-c3>/)+[^/]+(?(c1)(?!))(?(c2)(?!))(?(c3)(?!))
Как это работает?
Первая группа без захвата соответствует
@
персонажи. В этой группе без захвата у нас есть три именованные группы c1, c2 и c3, которые ничего не соответствуют, или, скорее, соответствуют пустой строке. Эти группы будут служить тремя счетчиками c1, c2 и c3. Потому что.NET отслеживает промежуточные записи при количественной оценке группы, каждый раз@
при совпадении захват добавляется в коллекции захвата для групп c1, c2 и c3.Следующий,
[^@=]+
съедает всех персонажей до первого=
,Вторая количественная группа
(?<-c1>=)+
соответствует=
персонажи. Эта группа, кажется, названа-c1
, но-c1
это не название группы.-c1
это синтаксис.NET для извлечения одного захвата из коллекции захвата группы c1 в эфир. Другими словами, это позволяет нам уменьшить c1. Если вы попытаетесь уменьшить значение c1, когда коллекция захвата пуста, совпадение не будет выполнено. Это гарантирует, что у нас никогда не будет больше=
чем@
персонажи. (Позже мы должны убедиться, что мы не можем иметь больше@
чем=
персонажи.)Следующие шаги повторяют шаги 2 и 3 для
-
а также/
символы, уменьшающие счетчики c2 и c3.[^/]+
съедает остаток строки.(?(c1)(?!))
является условием, которое говорит: "Если группа c1 была установлена, то сбой". Вы можете знать, что(?!)
это обычная уловка, чтобы заставить регулярное выражение потерпеть неудачу. Это условие гарантирует, что c1 был уменьшен до нуля: другими словами, не может быть больше@
чем=
персонажи.Аналогично
(?(c2)(?!))
а также(?(c3)(?!))
убедитесь, что не может быть больше@
чем-
а также/
персонажи.
Я не знаю о вас, но даже это немного долго, я нахожу это действительно интуитивным.