Подтвердить что ты не робот

Соответствующий текст между разделителями: жадное или ленивое регулярное выражение?

Для общей проблемы совпадения текста между разделителями (например, < и >) существуют две общие шаблоны:

  • используя жадный * или + квантификатор в форме START [^END]* END, например. <[^>]*>, или
  • с использованием ленивого *? или +? квантора в форме START .*? END, например. <.*?>.

Есть ли какая-то особая причина одобрить одну над другой?

4b9b3361

Ответ 1

Некоторые преимущества:

[^>]*:

  • Более выразительный.
  • Захватывает символы новой строки независимо от флага /s.
  • Считается более быстрым, потому что движок не нуждается в возврате, чтобы найти успешное совпадение (при [^>] движок не делает выбор - мы даем ему только один способ сопоставить шаблон со строкой).

.*?

  • Нет "дублирования кода" - конечный символ появляется только один раз.
  • Упрощение в случаях, когда конечный разделитель больше, чем символ длинный. (класс символов не будет работать в этом случае). Общей альтернативой является (?:(?!END).)*. Это еще хуже, если разделитель END является еще одним шаблоном.

Ответ 2

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

Пример. Если вы попытаетесь сопоставить <tag1><tag2>Hello! с <.*?>Hello!, регулярное выражение будет соответствовать

<tag1><tag2>Hello!

тогда как <[^>]*>Hello! будет соответствовать

<tag2>Hello!

Ответ 3

То, что большинство людей не учитывает при приближении к таким вопросам, как это происходит, когда регулярное выражение не может найти совпадение. Именно тогда наиболее вероятны появляться всплески производительности убийцы. Например, возьмите пример Тима, где вы ищете что-то вроде <tag>Hello!. Рассмотрим, что происходит с:

<.*?>Hello!

Механизм regex находит < и быстро находит закрытие >, но не >Hello!. Таким образом, .*? продолжает поиск >, за которым следует Hello!. Если его нет, он дойдет до конца документа до его сдачи. Затем двигатель regex возобновляет сканирование, пока не найдет другой <, и повторит попытку. Мы уже знаем, как это получится, но механизм регулярных выражений, как правило, не работает; он проходит через тот же rigamarole с каждым < в документе. Теперь рассмотрим другое регулярное выражение:

<[^>]*>Hello!

Как и раньше, он быстро совпадает с < с >, но не соответствует Hello!. Он вернется к <, затем закроется и начнет сканирование для другого <. Он по-прежнему будет проверять каждый <, как это сделал первое регулярное выражение, но он не будет искать весь путь до конца документа каждый раз, когда он найдет его.

Но это еще хуже. Если вы думаете об этом, .*? фактически эквивалентен негативному взгляду. Он говорит: "Прежде чем использовать следующий символ, убедитесь, что остальная часть регулярного выражения не может совпадать в этой позиции". Другими словами,

/<.*?>Hello!/

... эквивалентно:

/<(?:(?!>Hello!).)*(?:>Hello!|\z(*FAIL))/

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

((*FAIL) является одним из Perl backtracking-control verbs (также поддерживается в PHP). |\z(*FAIL) означает "или доходит до конца документа и отказаться".)

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

/<[^>]*+>Hello!/

... или обернуть его в атомную группу:

/(?><[^>]*>)Hello!/

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