Почему эти три регулярных выражения имеют разные значения шага?

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

Три шаблона выглядят следующим образом:

Почему первый шаблон настолько менее эффективен, чем второй, и, что наиболее смутно, почему третий настолько эффективен?

Ответ 1

TL; DR

Почему первый шаблон настолько менее эффективен, чем второй, и, наиболее смутно, , почему третий настолько эффективен?

Поскольку первые два привязаны, третье - нет.

Реальная история, как предпринимаются шаги

Учитывая это регулярное выражение /^x/gm, сколько шагов, по вашему мнению, потребуется движку для возврата "нет совпадения", если строка темы abc? Вы правы, два.

  • Утверждение начала строки
  • Соответствие x

Тогда полное совпадение завершится неудачно, так как без x сразу после начала утверждения строки.

Хорошо, я соврал. Это не то, что я противный, это просто облегчает понимание вещей, которые произойдут. Согласно regex101.com он не предпринимает никаких шагов:

введите описание изображения здесь

На этот раз вы поверите? Да. Нет. Посмотрим.

PCRE оптимизация запуска

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

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

  • строка subject содержит по крайней мере один символ, который соответствует первому символу соответствия или
  • если существует известная начальная точка.

Если не найдено, соответствующая функция никогда не запускается.

Говоря о том, что если наше регулярное выражение /x/, а наша строка темы abc, то при включенной оптимизации запуска предварительная проверка предназначена для поиска x, если не найдена целая матч терпит неудачу или лучше, он даже не хочет проходить процесс сопоставления.

Итак, как эта информация помогает?

Вернемся к нашему первому примеру и немного изменим наше регулярное выражение. From:

/^x/gm

к

/^x/g

Разница - это флаг m, который становится неустановленным. Для тех, кто не знает, что делает флаг m if, если:

Он изменяет значение символов ^ и $ в том смысле, что они больше не означают начало и конец строки, но начало и конец строки.

Теперь, если мы запустим это регулярное выражение /^x/g по нашей теме abc? Должны ли мы ожидать разницу в шагах, которые принимает двигатель или нет? Абсолютно да. Посмотрим regex101.com вернула информацию:

введите описание изображения здесь

Я действительно призываю вас поверить в этот раз. Это актуально.

Что происходит?

Ну, это кажется немного запутанным, но мы собираемся просветить все. Когда не существует набора модификаторов m, предварительное сканирование проверяет начало строки (известная начальная точка). Если утверждение прошло, тогда выполняется фактическая функция сопоставления, иначе возвращается "no match".

Но подождите... каждая строка предмета определенно имеет одно и только начало строковой позиции и всегда в самом начале. Значит, не было бы предварительного сканирования явно ненужным? Да, двигатель не выполняет предварительную проверку. С помощью /^x/g он сразу же утверждает начало строки, а затем терпит неудачу (так как она совпадает с ^, она проходит через фактический процесс сопоставления). Вот почему мы видим regex101.com показывает количество шагов 2.

Но... с настройкой модификатора m разные. Теперь значение обоих якорей ^ и $ изменяется. С ^ совпадением начала строки утверждение одного и того же положения в строке субъекта abc происходит, но следующий непосредственный символ не x, находящийся в рамках фактического процесса сопоставления, и поскольку флаг g включен, следующее совпадение начинается в позиции до b и не выполняется, и эта пробная версия продолжается до конца строки темы.

введите описание изображения здесь

Отладчик показывает шаги 6, но на главной странице указаны шаги 0, почему?

Я не уверен в последнем, но ради отладки, отладчик regex101 работает с (*NO_START_OPT), поэтому 6 шагов являются истинными, только если этот глагол установлен. И я сказал, что не уверен в последнем, потому что все привязанные шаблоны предотвращают дальнейшую предварительную оптимизацию, и мы должны знать, что можно назвать привязанный шаблон:

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

  • ^ если PCRE_MULTILINE не установлен
  • \A всегда
  • \G всегда
  • .*, если PCRE_DOTALL установлено, и нет обратных ссылок на subpattern, в котором .* появляется

Теперь вы поняли, о чем я говорил, когда я говорил, что предварительное сканирование не происходит, когда флаг m не установлен в /^x/g: он считал привязанный шаблон, оптимизация сканирования. Поэтому, когда флаг m включен, это уже не привязанный шаблон: /^x/gm, поэтому может произойти оптимизация предварительного сканирования.

введите описание изображения здесь

Двигатель знает, что начало привязки строки \A (или ^ в то время как многострочный режим отключается) происходит только один раз, когда он согласован, чтобы он не продолжался в следующей позиции.

Вернуться к собственным RegExes

Первые два привязаны (^ в сочетании с флагом m), третий - нет. То есть, третье регулярное выражение выигрывает от оптимизации предварительного сканирования. Вы можете поверить в шаги 35, так как оптимизация вызвала это. Но если вы отключите оптимизацию запуска:

(*NO_START_OPT)!next(?:(?<=^.{5})|(?=$))

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