Захват квантификаторов и арифметика квантификаторов

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

Получение квантификаторов

Кто-нибудь знает, позволяет ли разновидность регулярных выражений захватывать квантификаторы? Под этим я подразумеваю, что будет подсчитано количество символов, соответствующих квантификаторам, таким как + и *, и что это число может быть снова использовано в другом квантификаторе.

Например, предположим, что вы хотите убедиться, что у вас одинаковое количество букв L и R в строке такого типа: LLLRRRRR

Вы можете представить себе такой синтаксис, как

L(+)R{\q1}

где захвачен квантификатор + для L, и где захваченное число упоминается в квантификаторе для R как {\q1}

Это было бы полезно, чтобы сбалансировать количество {@,=,-,/} в таких строках, как @@@@ Звездные войны ==== 1977 ---- Научная фантастика //// Джордж Лукас

Отношение к рекурсии

В некоторых случаях захват квантификатора мог бы элегантно заменить рекурсию, например, фрагмент текста, обрамленный одинаковым количеством букв L и R, a в

L(+) some_content R{\q1} 

Идея представлена ​​в некоторых деталях на следующей странице: Захваченные квантификаторы

Также обсуждается естественное расширение захваченных квантификаторов: арифметика квантификаторов для случаев, когда вы хотите сопоставить (3*x + 1) количество совпавших ранее символов.

Я пытаюсь выяснить, существует ли что-то подобное.

Заранее спасибо за инфу!!!

Обновить

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

Обновление 2 (намного позже)

Я узнал, что в .NET есть функция, близкая к тому, о чем я спрашивал. Добавлен ответ для демонстрации функции.


person zx81    schedule 10.04.2014    source источник
comment
Я не знаю ни одного механизма регулярных выражений, который позволяет вам получить количество квантификаторов. В общем, вы не можете выполнять арифметические действия с регулярными выражениями. Некоторые механизмы регулярных выражений поддерживают рекурсию, вы можете использовать ее для сопоставления сбалансированных выражений. См. regular-expressions.info/refrecurse.html.   -  person Barmar    schedule 11.04.2014
comment
Я думаю, что вам лучше всего захватить его как группу и подсчитать символы на выбранном вами языке.   -  person Sam    schedule 11.04.2014


Ответы (2)


I don't know a regex engine that can capture a quantifier. However, it is possible with PCRE or Perl to use some tricks to check if you have the same number of characters. With your example:

@@@@ "Star Wars" ==== "1977" ---- "Science Fiction" //// "George Lucas"

you can check if @ = - / are balanced with this pattern that uses the famous Qtax trick, (are you ready?): the "possessive-optional self-referencing group"

~(?<!@)((?:@(?=[^=]*(\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?

Второй аспект этой группы заключается в том, что количество совпадающих символов = увеличивается при каждом повторении группы без захвата, поскольку каждый раз добавляется =. Чтобы гарантировать, что это число всегда увеличивается (или шаблон не работает), притяжательный квантификатор заставляет сначала сопоставляться обратную ссылку перед добавлением нового символа =.

Обратите внимание, что эту группу можно увидеть так: если существует группа 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). И если вы хотите получить всю строку как результат совпадения, вы должны добавить .* в конце (иначе результат совпадения будет пустым, как это замечает игривый).

person Casimir et Hippolyte    schedule 11.04.2014
comment
Большое спасибо за ваш прекрасный ответ --- должно быть, потребовалось довольно много времени, чтобы его сочинить! Я очень впечатлен и мне нужно некоторое время, чтобы изучить метод. Кстати, этот метод, который вы щедро изложили, кажется, доказывает три вещи: 1. что можно пытать PCRE при выполнении этой задачи, 2. что захваченные квантификаторы были бы прекрасным способом сделать такие задачи тривиальными по сравнению с гимнастикой олимпийского уровня. требуется в настоящее время (вы согласны?), и 3. насколько умны некоторые люди. Спасибо за образование. Это, вероятно, будет основным моментом регулярного выражения в моем году. (gloubiboulga ‹= частная шутка) - person zx81; 11.04.2014
comment
Чтобы добавить кое-что в смесь: для большей точности, после сопоставления @s, вместо окончательной проверки границ с опережением, мы можем сопоставить именно то, что мы хотим: (\s[^]+)\s\2(?5 )\s\3(?5)\s\4(?5)$ - person zx81; 11.04.2014
comment
Этот вопрос был добавлен в Часто задаваемые вопросы о регулярных выражениях переполнения стека в разделе Основные задачи проверки. Просто шучу. В разделе Advanced Regex-Fu. - person aliteralmind; 11.04.2014
comment
Две небольшие поправки кажутся необходимыми в (блестящей) рекурсивной версии. Во-первых, в данный момент выражение ничему не соответствует, потому что после выполнения предпросмотра сопоставление не выполняется. По крайней мере, мы могли бы добавить уродливую точку-звезду в конце или, что еще лучше, точно соответствовать тому, что мы хотим: ^@+(\s[^]+)\s=+(?4)\s-+(? 4)\s/+(?4)$ Вторая настройка заключается в том, что атомарную группу не нужно повторять (по крайней мере, мне так кажется), поэтому мы можем отбросить *. Пока мы на этом, мы можем начать регулярное выражение с ^(?xm) вместо границы. Что вы думаете? - person zx81; 12.04.2014
comment
Кроме того, учитывая исходный вопрос, если вы отредактируете свой первый абзац, указав, что, хотя задача может быть выполнена, учитывая сложность задачи, некоторая форма захвата квантификатора в синтаксисе регулярных выражений действительно значительно облегчит ситуацию, я считаю, что ваш ответ будет действительно выделяться как пример удивительного, полного ответа. :) - person zx81; 12.04.2014
comment
@playful: читатель может составить собственное мнение, нет необходимости писать это. Я здесь не для того, чтобы воевать, это ваша (и Джефф) борьба. - person Casimir et Hippolyte; 13.04.2014
comment
@playful: о возможном изменении шаблонов: я не пытался быть здесь слишком точным. Это описание возможных способов (в общем) сравнения величин. Действительно, для строки примера вам не нужен квантификатор * для атомарных групп, но имейте в виду, что это только по двум причинам: 1) символы идут подряд, 2) между группами символов есть хотя бы один символ. Я думаю, что любой, кто прочитает этот пост и поймет паттерн, сможет адаптировать его к конкретной ситуации. - person Casimir et Hippolyte; 13.04.2014
comment
@Casimir Конечно, я выберу ваш блестящий ответ, но вам не кажется, что рекурсивное регулярное выражение выглядело бы лучше, если бы оно использовало некоторые символы после осмотра? Сейчас нечего сопоставлять (просто осмотр). Если вы замените (?‹!@) вверху на (?m)^ и добавите @+(\s[^]+)\s=+(?4)\s-+(?4)\s/ +(?4)$ после просмотра в конце можно использовать выражение и вернуть все совпадения (проверено в RegexBuddy). Полезнее, чем слепые удары без ответных спичек. Конечно, это ваше решение, дайте мне знать ваше окончательное мнение, и я все равно задам вам вопрос. Спасибо! - person zx81; 13.04.2014
comment
Приказываю всех успокоить, хочу заявить, что я никогда не участвовал в gloubiboulga party! - person Casimir et Hippolyte; 13.04.2014
comment
LMGTFY :) - person aliteralmind; 13.04.2014
comment
@playful: я добавил примечание о пустом совпадении. О @+(\s"[^"]+")\s=+(?4)\s-+(?4)\s/+(?4)$ и уродливом .*. Если после этого вам не нужно сопоставлять что-то еще, использование точного описания строки бесполезно и даст больше работы механизму регулярных выражений, чем простое .*. Если вам нужно продолжить шаблон после этой части, полное описание, очевидно, более безопасно. - person Casimir et Hippolyte; 13.04.2014
comment
@CasimiretHippolyte Я никогда не участвовал в gloubiboulga party Дружелюбные монстры? Да, здесь рай. Еще раз спасибо за ваш прекрасный ответ, который мгновенно стал классикой. - person zx81; 13.04.2014

Возвращаясь через пять недель, потому что я узнал, что в .NET есть что-то, что очень близко к идее «захвата квантификатора», упомянутой в вопросе. Функция называется «балансировка групп».

Вот решение, которое я придумал. Выглядит долго, но довольно просто.

(?:@(?<c1>)(?<c2>)(?<c3>))+[^@=]+(?<-c1>=)+[^=-]+(?<-c2>-)+[^-/]+(?<-c3>/)+[^/]+(?(c1)(?!))(?(c2)(?!))(?(c3)(?!))

Как это работает?

  1. Первая группа без захвата соответствует символам @. В этой незахватывающей группе у нас есть три именованные группы c1, c2 и c3, которые ничему не соответствуют, вернее, соответствуют пустой строке. Эти группы будут служить тремя счетчиками c1, c2 и c3. Поскольку .NET отслеживает промежуточные захваты при количественной оценке группы, каждый раз, когда совпадает @, захват добавляется к наборам захватов для групп c1, c2 и c3.

  2. Далее [^@=]+ съедает все символы до первого =.

  3. Вторая квантифицированная группа (?<-c1>=)+ соответствует = символам. Кажется, эта группа называется -c1, но -c1 не является названием группы. -c1 is.NET синтаксис для отправки одного захвата из коллекции захватов группы c1 в эфир. Другими словами, это позволяет нам уменьшить c1. Если вы попытаетесь уменьшить c1, когда коллекция захвата пуста, совпадение не удастся. Это гарантирует, что у нас никогда не будет больше = символов, чем @. (Позже нам нужно будет убедиться, что у нас не может быть больше @, чем = символов.)

  4. Следующие шаги повторяют шаги 2 и 3 для символов - и /, уменьшая значения счетчиков c2 и c3.

  5. [^/]+ съедает остаток строки.

  6. (?(c1)(?!)) - это условное выражение, которое говорит: «Если группа c1 была установлена, произойдет сбой». Возможно, вы знаете, что (?!) — это обычный трюк, чтобы заставить регулярное выражение не работать. Это условное выражение гарантирует, что c1 будет уменьшено до нуля: другими словами, @ символов не может быть больше, чем =.

  7. Точно так же (?(c2)(?!)) и (?(c3)(?!)) гарантируют, что @ не может быть больше, чем - и / символов.

Я не знаю о вас, но даже это немного длинно, я нахожу это действительно интуитивным.

person zx81    schedule 17.05.2014