Регулярное выражение (а?) * не экспоненциальное?

В настоящее время я изучаю проблему регулярных выражений, которые могут выполняться в экспоненциальное время при сопоставлении с определенным вводом, например, как (a*)*, так и (a|a)* потенциально демонстрируют 'катастрофический поиск с возвратом' при сопоставлении со строкой aaaaab — для каждой дополнительной буквы 'a' в совпадающей строке время, необходимое для попытки сопоставления строки, удваивается. Это имеет место только в том случае, если движок использует подход с возвратом / NFA, пытаясь попробовать все возможные ветви в дереве до сбоя, например, используемый в PCRE.

Мой вопрос: почему (a?)* не уязвим? Основываясь на моем понимании возврата, в строке «aaaab» должно происходить то же, что и в случае с (a|a)*. Если мы создадим NFA, используя стандартную конструкцию NFA Томпсона, то, конечно, для каждого эпсилон-перехода машина должна будет продолжать принимать их и возвращаться назад так же, как в случае двух a? Например (опуская некоторые шаги и где @ заменяет эпсилон):

"aaaa" соответствует, но не может соответствовать "b", ошибка (возврат)
"aaaa@" соответствует, ошибка "b" (возврат)
соответствует "aaa@a", ошибка "b" (возврат) )
"aaa@a@" соответствует, "b" не соответствует (возврат)
...
"@a@a@a@a@" соответствует, "b" не соответствует (возврат)

пробовать все возможные комбинации эпсилонов и а, что наверняка приводит к экспоненциальному взрыву маршрутов?

Было бы разумно удалить эпсилон-переходы из NFA, но я считаю, что это приведет к удалению всего недетерминизма из шаблона (a*)*. Это определенно уязвимо, поэтому я не совсем уверен, что происходит!

Заранее большое спасибо!

Редактировать: Qtax указал, что эпсилоны не могут по-прежнему присутствовать, когда NFA проходится с традиционным возвратом, иначе (@)* будет пытаться соответствовать вечно. Итак, какая реализация NFA может привести к тому, что (a*)* и (a|a)* будут экспоненциальными, а (a?)* — нет? Это суть вопроса на самом деле.


person Jarmex    schedule 26.08.2012    source источник
comment
Механизмы регулярных выражений PCRE и Perl оптимизированы во многих отношениях. Все ваши примеры будут быстро давать сбой без катастрофических возвратов, даже на огромных строках.   -  person Qtax    schedule 27.08.2012
comment
Я знаю об этом, возможно, лучшим примером будет механизм регулярных выражений Java, который не дает сбоев быстро. Ранние версии PCRE и Perl также страдали теми же проблемами, и мой вопрос может быть применен к ним! Однако на тех движках, которые кажутся уязвимыми для шаблонов, таких как (a*)* (т. е. неоптимизированных движках), (a?)* кажется нормальным — почему?!   -  person Jarmex    schedule 27.08.2012
comment
Оптимизации. Количественное выражение нулевой ширины (без побочных эффектов) бесполезно, и если бы оно использовалось так, как вы описываете (попытайтесь сопоставить a, а затем пустую строку для каждого повторения), тогда регулярное выражение никогда не потерпит неудачу, поскольку вы можете повторять ()* неопределенно.   -  person Qtax    schedule 27.08.2012
comment
Хорошая мысль о ()*. Мне трудно понять, как оптимизируются существующие реализации, чтобы этого не произошло. Очень наивная реализация простого построения NFA с помощью конструкции Томпсона (электронные ходы и все такое), а затем попытка вернуться через нее никогда не завершится по ()* причине, которую вы упомянули, так что же они делают, что приводит к успеху (a?)*, но (a*)* терпеть неудачу? Это не может быть так просто, как полное удаление электронных ходов, поскольку я считаю, что это имеет побочный эффект, заставляя (a*)* быть детерминированным.   -  person Jarmex    schedule 27.08.2012
comment
Если вам нужен пример того, как механизм регулярных выражений обрабатывает такие случаи, вы можете посмотреть Perl's отладка регулярных выражений, которая показывает, какие выражения компилируются, и каждый шаг сопоставления: ^(a*)*$ и ^(a?)*$. Пример команды (победа): perl -Mre=debug -e "'aaab' =~ /^(a?)*$/"   -  person Qtax    schedule 27.08.2012


Ответы (1)


Хорошо, после некоторого расследования мне в конце концов удалось выяснить, что это связано с использованием «барьеров» в реализациях NFA. Проще говоря, барьеры размещаются в стратегических точках NFA (например, в узле сразу после перехода «a» в конструкции NFA a*). Они требуют, чтобы матч продолжался с момента предыдущего удара по этому барьеру. Это предотвращает попадание NFA в ситуацию, когда она соответствует бесконечному числу эпсилонов, и позволяет завершить ее.

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

person Jarmex    schedule 28.08.2012