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

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

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

Есть ли особая причина отдавать предпочтение одному перед другим?

17
задан AndersTornkvist 24 October 2011 в 21:37
поделиться

2 ответа

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

[^>]*:

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

.*?

  • Нет «дублирования кода» - конечный символ появляется только один раз.
  • Проще в тех случаях, когда конечный разделитель длиннее символа. (класс символов не будет работать в этом случае) Обычной альтернативой является (?:(?!END).)*. Это еще хуже, если разделитель END - это другой шаблон.
12
ответ дан 30 November 2019 в 13:11
поделиться

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

<.*?>Hello!

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

<[^>]*>Hello!

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

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

/<.*?>Hello!/

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

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

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

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

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

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

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

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

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

6
ответ дан 30 November 2019 в 13:11
поделиться
Другие вопросы по тегам:

Похожие вопросы: