Это вторая часть серии образовательных статей о регулярных выражениях. Он показывает , как lookaheads и вложенные ссылки могут быть использованы , чтобы соответствовать нерегулярного languge а п б п . Вложенные ссылки впервые представлены в: Как это регулярное выражение находит треугольные числа?
Один из архетипических нерегулярных языков :
L = { a
nb
n: n > 0 }
Это язык всех непустых строк, состоящих из некоторого количества a
символов, за которыми следует равное количество b
символов. Примеры строк в этом языке являются ab
, aabb
, aaabbb
.
Нерегулярность этого языка можно показать с помощью леммы о накачке . Фактически это архетипический контекстно-свободный язык , который может быть создан с помощью контекстно-свободной грамматики S → aSb | ab
.
Тем не менее, современные реализации регулярных выражений явно распознают не только обычные языки. То есть они не являются «регулярными» по определению теории формального языка. PCRE и Perl поддерживают рекурсивное регулярное выражение, а .NET поддерживает определение балансирующих групп. Еще менее «причудливые» функции, например сопоставление обратных ссылок, означают, что регулярное выражение не является регулярным.
Но насколько мощны эти «базовые» функции? Можем ли мы, например, распознать L
с помощью регулярного выражения Java? Можем ли мы , возможно , объединить lookarounds и вложенные ссылки и иметь рисунок , который работает с , например , String.matches
чтобы соответствовать строки , как ab
, aabb
, aaabbb
и т.д.?
Ссылки
- perlfaq6: Могу ли я использовать регулярные выражения Perl для сопоставления сбалансированного текста?
- MSDN - Элементы языка регулярных выражений - Балансировка определений групп
- pcre.org - справочная страница PCRE
- regular-expressions.info - Lookarounds и группирование и обратные ссылки
java.util.regex.Pattern
Связанные вопросы
источник
Ответы:
Ответ, разумеется, ДА! Вы, безусловно, можете написать шаблон регулярного выражения Java, соответствующий a n b n . Он использует положительный просмотр вперед для утверждения и одну вложенную ссылку для «подсчета».
Вместо того, чтобы сразу выдавать образец, этот ответ проведет читателя через процесс его построения. По мере того, как решение строится медленно, даются различные подсказки. В этом аспекте, надеюсь, этот ответ будет содержать гораздо больше, чем просто еще один аккуратный шаблон регулярного выражения. Надеюсь, читатели также узнают, как «думать в регулярном выражении» и как гармонично сочетать различные конструкции, чтобы в будущем они могли самостоятельно вывести больше шаблонов.
Язык, используемый для разработки решения, будет PHP из-за его краткости. Заключительный тест после того, как шаблон будет завершен, будет выполнен на Java.
Шаг 1. Посмотрите вперед для утверждения
Начнем с более простой проблемы: мы хотим найти соответствие
a+
в начале строки, но только если за ней сразу следуетb+
. Мы можем использовать^
для привязки нашего совпадения, а так как мы хотим сопоставить толькоa+
без символаb+
, мы можем использовать утверждение опережающего просмотра(?=…)
.Вот наш образец с простой тестовой обвязкой:
Результат ( как показано на ideone.com ):
Это именно тот результат, который нам нужен: мы сопоставляем
a+
, только если он находится в начале строки и только если за ним сразу следуетb+
.Урок : вы можете использовать шаблоны в поисках, чтобы делать утверждения.
Шаг 2. Захват в упреждающем (и в режиме свободного интервала)
Теперь предположим, что даже если мы не хотим,
b+
чтобы это было частью совпадения, мы все равно хотим захватить его в группу 1. Кроме того, поскольку мы ожидаем более сложного шаблона, давайте использоватьx
модификатор для свободного интервала, чтобы мы может сделать наше регулярное выражение более читабельным.Основываясь на нашем предыдущем фрагменте PHP, теперь у нас есть следующий шаблон:
Теперь вывод ( как видно на ideone.com ):
Обратите внимание, что например,
aaa|b
это результат того,join
что каждая группа захватила'|'
. В этом случае захватывается группа 0 (т. Е. То, что соответствует шаблону)aaa
, и захватывается группа 1b
.Урок : вы можете делать снимки внутри обзора. Вы можете использовать свободный интервал, чтобы улучшить читаемость.
Шаг 3. Реорганизация опережающего просмотра в «цикл»
Прежде чем мы сможем представить наш механизм подсчета, нам нужно сделать одну модификацию нашего шаблона. В настоящее время просмотр вперед находится за пределами
+
«цикла» повторения. Пока это нормально, потому что мы просто хотели утверждать, что естьb+
последователиa+
, но в конечном итоге мы действительно хотим утверждать, что для каждого,a
что мы сопоставляем внутри «цикла», есть соответствующий, которыйb
будет идти с ним.Давайте пока не будем беспокоиться о механизме подсчета и просто проведем рефакторинг следующим образом:
a+
до(?: a )+
(обратите внимание, что(?:…)
это группа без захвата)a*
прежде чем мы сможем «увидеть»b+
, поэтому измените шаблон соответствующим образом.Итак, теперь у нас есть следующее:
Результат такой же, как и раньше ( как показано на ideone.com ), поэтому в этом отношении нет никаких изменений. Важно то, что теперь мы делаем утверждение на каждой итерации в
+
«петле». С нашим текущим шаблоном в этом нет необходимости, но теперь мы сделаем группу 1 «подсчитываемой» для нас, используя саморегуляцию.Урок : вы можете снимать внутри группы без захвата. Поисковые запросы можно повторять.
Шаг 4: Это шаг, с которого мы начинаем считать
Вот что мы собираемся сделать: мы перепишем группу 1 так, чтобы:
+
, когда перваяa
соответствует, она должна захватитьb
a
сопоставляется другой , он должен захватыватьbb
bbb
b
для захвата в группу 1, утверждение просто не выполняется.Так что группу 1, которая сейчас существует
(b+)
, придется переписать во что-то вроде(\1 b)
. То есть мы пытаемся «добавить»b
к тому, что группа 1 захватила в предыдущей итерации.Здесь есть небольшая проблема в том, что в этом шаблоне отсутствует «базовый случай», то есть случай, когда он может соответствовать без ссылки на себя. Базовый вариант необходим, потому что группа 1 запускается как «неинициализированная»; он еще ничего не захватил (даже пустую строку), поэтому попытка ссылки на себя всегда будет неудачной.
Есть много способов обойти это, но сейчас давайте просто сделать самостоятельно эталонную опционально , то есть
\1?
. Это может сработать, а может и не сработать, но давайте просто посмотрим, что это делает, и если возникнут какие-то проблемы, мы пересечем этот мост, когда подойдем к нему. Кроме того, мы добавим еще несколько тестовых примеров, пока мы это делаем.Теперь вывод ( как видно на ideone.com ):
Ага! Похоже, мы действительно близки к решению! Нам удалось «подсчитать» группу 1, используя саморегуляцию! Но подождите ... что-то не так со вторым и последним тестами !! Не хватает
b
s, и как-то неправильно посчитали! Мы выясним, почему это произошло на следующем шаге.Урок : Один из способов «инициализировать» группу со ссылками на себя - сделать сопоставление со ссылками на себя необязательным.
Шаг 4½: понять, что пошло не так
Проблема в том, что, поскольку мы сделали сопоставление ссылок на себя необязательным, «счетчик» может «сбросить» обратно на 0, когда их недостаточно
b
. Давайте внимательно посмотрим, что происходит на каждой итерации нашего шаблона сaaaaabbb
входными данными.Ага! На нашей 4-й итерации мы все еще могли совпадать
\1
, но не могли\1b
! Поскольку мы допускаем, чтобы сопоставление ссылок на себя было необязательным\1?
, движок откатывается назад и выбирает вариант «Нет, спасибо», который затем позволяет нам сопоставить и захватить простоb
!Однако обратите внимание, что, за исключением самой первой итерации, вы всегда можете сопоставить только ссылку на себя
\1
. Это, конечно, очевидно, поскольку это то, что мы только что захватили в нашей предыдущей итерации, и в нашей настройке мы всегда можем сопоставить это снова (например, если мы захватили вbbb
прошлый раз, мы гарантируем, что они все еще будутbbb
, но может или может не быть вbbbb
этот раз).Урок : остерегайтесь возврата. Механизм регулярных выражений будет выполнять столько откатов, сколько вы позволите, пока заданный шаблон не совпадет. Это может повлиять на производительность (например, катастрофический возврат ) и / или правильность.
Шаг 5: На помощь приходит самообладание!
«Исправление» теперь должно быть очевидным: объединить необязательное повторение с притяжательным квантификатором. То есть вместо простого
?
использования?+
(помните, что повторение, которое количественно определяется как притяжательное, не приводит к возврату, даже если такое «сотрудничество» может привести к совпадению с общим шаблоном).В очень Неформально, это то , что
?+
,?
и??
говорит:В нашей настройке
\1
он не будет там в первый раз, но он всегда будет там в любое время после этого, и мы всегда хотим сопоставить его тогда. Таким образом,\1?+
достигнем именно того, что мы хотим.Теперь вывод ( как показано на ideone.com ):
Вуаля !!! Задача решена!!! Теперь мы считаем правильно, именно так, как мы этого хотим!
Урок : узнайте разницу между жадным, упорным и притяжательным повторением. Необязательное-притяжательное может быть мощной комбинацией.
Шаг 6: Последние штрихи
Итак, то, что у нас есть прямо сейчас, - это шаблон, который
a
повторяется многократно, и для каждого совпаденияa
есть соответствующийb
захваченный в группе 1. Шаблон+
завершается, когда их больше нетa
, или если утверждение не удалось, потому что нет соответствующегоb
для анa
.Чтобы завершить работу, нам просто нужно добавить к нашему шаблону
\1 $
. Теперь это обратная ссылка на то, что соответствует группе 1, за которой следует конец привязки строки. Якорь гарантирует, чтоb
в строке нет лишних символов; другими словами, что на самом деле у нас есть a n b n .Вот окончательный шаблон с дополнительными тестовыми примерами, в том числе одним из 10 000 символов:
Он находит 4 матча:
ab
,aabb
,aaabbb
, и в 5000 б 5000 . Запуск на ideone.com занимает всего 0,06 секунды .Шаг 7. Тест Java
Итак, шаблон работает в PHP, но конечная цель - написать шаблон, который работает на Java.
Шаблон работает должным образом ( как показано на ideone.com ).
А теперь подходим к выводу ...
Надо сказать, что и
a*
в предварительном просмотре, и в «основном+
цикле» разрешен возврат с возвратом. Читателям предлагается подтвердить, почему это не проблема с точки зрения правильности, и почему в то же время использование обоих притяжательных элементов также будет работать (хотя, возможно, смешивание обязательного и необязательного притяжательного квантора в одном и том же шаблоне может привести к неправильному восприятию).Следует также сказать, что хотя наличие шаблона регулярного выражения, который будет соответствовать a n b n , это здорово, на практике это не всегда «лучшее» решение. Гораздо лучшее решение - просто сопоставить
^(a+)(b+)$
, а затем сравнить длину строк, захваченных группами 1 и 2 на языке программирования хостинга.В PHP это может выглядеть примерно так ( как показано на ideone.com ):
Цель этой статьи НЕ состоит в том, чтобы убедить читателей, что регулярное выражение может делать почти все; он явно не может, и даже для вещей, которые он может сделать, следует рассмотреть по крайней мере частичное делегирование на язык хоста, если это приведет к более простому решению.
Как упоминалось выше, хотя эта статья обязательно помечена
[regex]
для stackoverflow, возможно, она о большем. Хотя, безусловно, есть ценность в изучении утверждений, вложенных ссылок, притяжательного квантификатора и т. Д., Возможно, более важным уроком здесь является творческий процесс, с помощью которого можно попытаться решить проблемы, решимость и тяжелая работа, которые часто требуются, когда вы сталкиваетесь с различные ограничения, систематический состав из различных частей для построения рабочего раствора и т. д.Бонусный материал! Рекурсивный шаблон PCRE!
Поскольку мы действительно подняли PHP, необходимо сказать, что PCRE поддерживает рекурсивные шаблоны и подпрограммы. Таким образом, следующий шаблон работает для
preg_match
( как показано на ideone.com ):В настоящее время регулярное выражение Java не поддерживает рекурсивный шаблон.
Еще больше бонусного материала! Соответствие a n b n c n !!
Итак , мы видели , как в соответствии с п б п , которая не является регулярным, но по- прежнему контекстно-свободной, но мы можем также соответствовать с п б п С п , которая не является даже контекстно-свободной?
Конечно, ДА! Читателям предлагается попытаться решить эту проблему самостоятельно, но решение представлено ниже (с реализацией на Java на ideone.com ).
источник
feature
? .... Не уверен, хорошая ли это идея. Я знаю, что это за последний символ, но его нельзя прочитать (кроме копирования и вставки).preg_match()
- это пример PCRE . Регулярные выражения Java, похоже, основаны на более старой версии регулярных выражений Perl . Это означает, что регулярные выражения PHP более мощные, чем версия на Java. По состоянию на 2013-02-21 , pcre.txt утверждает , что примерно соответствует Perl 5.12 . В то время как Perl в настоящее время находится на уровне 5.16, а 5.18 - через несколько месяцев. (На самом деле, к регулярным выражениям в то время особо не добавлялось)Учитывая, что не упоминалось о поддержке рекурсивных шаблонов PCRE, я просто хотел бы указать на простейший и наиболее эффективный пример PCRE, который описывает рассматриваемый язык:
источник
a^n b^n c^n
.a
s иb
s без захвата (и проверяет, есть ли одинаковое количество с рекурсией), за которым следует захватывающее регулярное выражение, которое жадно потребляет все a, а затем применяет рекурсивный шаблон для потребления и проверки того, что существует одинаковое количествоb
s иc
s. Регулярное выражение:/^(?=(a(?-1)?b)c)a+(b(?-1)?c)$/x
. ПредоставленоКак упоминалось в вопросе - с группой балансировки .NET шаблоны типа a n b n c n d n … z n могут быть легко сопоставлены как
Например: http://www.ideone.com/usuOE
Редактировать:
Существует также шаблон PCRE для обобщенного языка с рекурсивным шаблоном, но необходим просмотр вперед. Я не думаю, что это прямой перевод вышесказанного.
Например: http://www.ideone.com/9gUwF
источник
a^n b^n
с регулярным выражением .NET?» статья в будущем, но вы можете написать ее, если хотите. Я пишу эти статьи не только для себя; Я действительно хочу побудить других делать то же самое, чтобы на сайте был хороший контент.(?!b)
,(?!c)
и т.д. после того, как группы захвата , как так: regex101.com/r/sdlRTm/2