Можно ли стереть группу захвата, которая уже соответствует, делая ее не участвующей? - PullRequest
0 голосов
/ 05 января 2019

В PCRE2 или любом другом механизме регулярных выражений, поддерживающем прямые обратные ссылки, можно изменить группу захвата, которая соответствовала на предыдущей итерации цикла, на не участвующую группу захвата (также известную как неустановленная группа захвата или не захваченная группа ), в результате чего условные выражения, проверяющие соответствие этой группы их условию «ложь», а не условию «истина»?

Например, возьмем следующее регулярное выражение PCRE:

^(?:(z)?(?(1)aa|a)){2}

Когда передается строка zaazaa, она соответствует всей строке, как требуется. Но когда кормят zaaaa, я бы хотел, чтобы он совпадал с zaaa; вместо этого он соответствует zaaaa, всей строке. (Это только для иллюстрации. Конечно, этот пример может быть обработан с помощью ^(?:zaa|a){2}, но это не относится к делу. Практическое использование стирания группы захвата обычно происходит в циклах, которые чаще всего выполняют намного больше двух итераций.)

Альтернативный способ сделать это, который также не работает должным образом:

^(?:(?:z()|())(?:\1aa|\2a)){2}

Обратите внимание, что оба они работают по желанию, когда цикл "развернут", потому что им больше не нужно стирать уже сделанный захват:

^(?:(z)?(?(1)aa|a))(?:(z)?(?(2)aa|a))
^(?:(?:z()|())(?:\1aa|\2a))(?:(?:z()|())(?:\3aa|\4a))

Таким образом, вместо того, чтобы использовать простейшую условную форму, нужно использовать более сложную форму, которая работает только в этом примере, потому что "истинное" совпадение z не пусто:

^(?:(z?)(?(?!.*$\1)aa|a)){2}

Или просто используя эмулируемое условие:

^(?:(z?)(?:(?!.*$\1)aa|(?=.*$\1)a)){2}

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

Это отличается от того, что я интуитивно ожидал. Я бы реализовал это так: оценка группы захвата с 0 повторениями приведет к ее удалению / отмене (поэтому это может произойти с любой группой захвата с квантификатором *, ? или {0,N}), но пропуская ее из-за наличие параллельной альтернативы в той же группе, в которой он получил перехват во время предыдущей итерации, не удалит его. Таким образом, это регулярное выражение будет по-прежнему соответствовать словам, если они содержат хотя бы один из каждого гласного :

\b(?:a()|e()|i()|o()|u()|\w)++\1\2\3\4\5\b

Но пропуск группы захвата из-за ее нахождения в неоцененной альтернативе группы, которая оценивается с ненулевыми повторениями, вложенной в группу, в которой группа захвата приняла значение во время предыдущей итерации будет стереть / сбросить его, чтобы это регулярное выражение могло захватывать или стирать группу \1 на каждой итерации цикла:

^(?:(?=a|(b)).(?(1)_))*$

и будет соответствовать строкам, таким как aaab_ab_b_aaaab_ab_aab_b_b_aaa. Тем не менее, обратные ссылки на самом деле реализованы в существующих движках, это соответствует aaaaab_a_b_a_a_b_b_a_b_b_b_.

Я хотел бы знать ответ на этот вопрос не только потому, что он был бы полезен при построении регулярных выражений, но и потому, что я написал свой собственный механизм регулярных выражений , в настоящее время совместимый с ECMAScript с некоторыми дополнительными расширениями (включая молекулярный взгляд (?*), то есть неатомный взгляд, который, насколько я знаю, не имеет никакой другой движок), и я хотел бы продолжить добавление функций из других движков, включая прямые / вложенные обратные ссылки. Я не только хочу, чтобы моя реализация обратных обратных ссылок была совместима с существующими реализациями, но если не способ удаления групп захвата в других движках, я, вероятно, создам способ сделать это в моем движок, который не конфликтует с другими существующими функциями регулярных выражений.

Чтобы быть ясным: ответ, утверждающий, что это невозможно в каких-либо основных двигателях, будет приемлемым, если он подкреплен адекватными исследованиями и / или цитированием источников. Ответ о том, что это возможно , было бы гораздо проще сформулировать, поскольку для этого потребовался бы только один пример.

Некоторая информация о том, что такое не участвующая группа захвата:
http://blog.stevenlevithan.com/archives/npcg-javascript - это статья, которая первоначально познакомила меня с идеей.
https://www.regular -expressions.info / backref2.html - первый раздел этой страницы дает краткое объяснение.
В регулярных выражениях ECMAScript / Javascript обратные ссылки на NPCG всегда совпадают (сопоставление нулевой длины). Практически во всех других разновидностях регулярных выражений они ничего не соответствуют.

Ответы [ 3 ]

0 голосов
/ 16 января 2019

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

(?(DEFINE)((z)?(?(2)aa|a)))^(?1){2}

Смотрите демо здесь

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

0 голосов
/ 03 февраля 2019

Это частично возможно при использовании регулярных выражений в .NET.

Первое, на что нужно обратить внимание, это то, что .NET записывает все записи для данной группы, а не только последние. Например, ^(?=(.)*) записывает каждый символ в первой строке как отдельный захват в группе.

Для фактического удаления перехватов .NET regex имеет конструкцию, известную как балансировка групп . Полный формат этой конструкции: (?<name1-name2>subexpression).

  • Во-первых, name2 должен быть ранее захвачен.
  • Подвыражение должно соответствовать.
  • Если присутствует name1, подстрока между концом захвата name2 и началом совпадения подвыражения записывается в name1.
  • Последний снимок name2 удаляется. (Это означает, что старое значение может иметь обратную ссылку в подвыражении.)
  • Совпадение продвигается до конца подвыражения.

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

0 голосов
/ 15 января 2019

Я нашел это задокументировано на справочной странице PCRE в разделе «РАЗЛИЧИЯ МЕЖДУ PCRE2 И PERL»:

   12.  There are some differences that are concerned with the settings of
   captured strings when part of  a  pattern  is  repeated.  For  example,
   matching  "aba"  against  the  pattern  /^(a(b)?)+$/  in Perl leaves $2
   unset, but in PCRE2 it is set to "b".

Я изо всех сил пытаюсь придумать практическую проблему, которая не может быть лучше решена с помощью альтернативного решения, но в интересах упрощения, вот так:

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

/^(?:(.)(?=.*(\1(?(2)\2))))*+.?\2$/

Достаточно просто. Теперь предположим, что нас просят проверить, что каждая строка на входе является палиндромом. Давайте попробуем решить эту проблему, поместив выражение в повторяющуюся группу:

\A(?:^(?:(.)(?=.*(\1(?(2)\2))))*+.?\2$(?:\n|\z))+\z

Очевидно, что это не работает, поскольку значение \ 2 сохраняется от первой строки к следующей. Это похоже на проблему, с которой вы сталкиваетесь, и вот несколько способов ее преодоления:

1. Заключите все подвыражение в (?!(?! )):

\A(?:(?!(?!^(?:(.)(?=.*(\1(?(2)\2))))*+.?\2$)).+(?:\n|\z))+\z

Очень просто, просто засунь их туда, и ты, по сути, идеален. Не очень удачное решение, если вы хотите, чтобы какие-то определенные значения сохранялись

2. Группа сброса ветви для сброса значения групп захвата:

\A(?|^(?:(.)(?=.*(\1(?(2)\2))))*+.?\2$|\n()()|\z)+\z

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

3. Представьте группу, которая захватывает остаток строки из определенной позиции, чтобы помочь вам позже определить, где вы находитесь:

\A(?:^(?:(.)(?=.*(\1(?(2)(?=\2\3\z)\2))([\s\S]*)))*+.?\2$(?:\n|\z))+\z 

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

Это один из моих любимых приемов, потому что его можно использовать для решения задач, которые кажутся невозможными, таких как старые , соответствующие вложенным скобкам с использованием прямых ссылок . С его помощью вы можете хранить любую другую информацию о захвате, которая вам нужна. Единственным недостатком является то, что это ужасно неэффективно, особенно для длинных предметов.

4. Это на самом деле не отвечает на вопрос, но решает проблему:

\A(?![\s\S]*^(?!(?:(.)(?=.*(\1(?(2)\2))))*+.?\2$))

Это альтернативное решение, о котором я говорил. По сути, «переписать шаблон» :) Иногда это возможно, иногда нет.

...