Жадные и ленивые квантификаторы
Жадные и ленивые квантификаторы в регулярных выражениях JavaScript: жадный берёт максимум и отступает, ленивый (*?, +?, ??, {n,m}?) берёт минимум.
Квантификатор вроде *, + или ? указывает регулярному выражению, сколько раз может повторяться предшествующий шаблон. Но когда сразу несколько вариантов длины текста удовлетворяют шаблону, движок должен решить, какой из них взять. Именно это и контролируют жадные и ленивые квантификаторы.
По умолчанию каждый квантификатор жадный: он захватывает как можно больше символов. Добавьте ? после квантификатора — и он становится ленивым: захватывает как можно меньше. Выбор неправильного режима — одна из самых распространённых ошибок при работе с регулярными выражениями; классический симптом — шаблон, который «совпадает слишком много». Эта страница объясняет механизм обоих режимов, чтобы вы могли осознанно выбирать нужный.
Как на самом деле работает жадное совпадение: захват, затем откат
Жадный квантификатор не знает магическим образом, где остановиться. Он работает в два этапа:
- Захватить максимум.
.*сначала поглощает весь остаток строки. - Откат. Если шаблон, следующий за
.*, больше не может совпасть (потому что всё уже поглощено), движок возвращает символы по одному, каждый раз повторяя попытку, пока весь шаблон не совпадёт.
Таким образом, жадный режим означает «взять всё, а затем неохотно отдавать минимально необходимое для того, чтобы остальная часть шаблона совпала». Понимание шага отката — ключ к предсказанию поведения жадных шаблонов.
Жадный * в действии
Здесь .* совпадает с любым символом ноль или более раз. Поскольку после него ничего не требуется, откат не нужен, и он захватывает всё до конца строки, возвращая "ABCD*E".
Жадный + в действии
C+ совпадает с "C" один или более раз и жадно берёт все три, поэтому совпадение — "ABCCC".
Как работает ленивое совпадение: захват минимума, затем расширение
Ленивый квантификатор инвертирует стратегию:
- Захватить минимум.
.*?начинает с совпадения с ничем. - Расширение. Только если остальная часть шаблона не может совпасть, движок позволяет ленивому квантификатору взять ещё один символ, затем повторяет попытку — и так до тех пор, пока весь шаблон не совпадёт.
Таким образом, ленивый режим означает «брать как можно меньше и расширяться только когда вынуждены». Квантификатор становится ленивым при добавлении ?.
Ленивый *? в действии
Это сбивающий с толку случай. Результат — просто "AB". Почему? Потому что .*? разрешено совпасть с ничем, и после него в шаблоне нет ничего, что вынуждает захватывать больше. Как только AB совпало, шаблон уже выполнен, поэтому ленивый квантификатор с удовольствием останавливается на нуле символов. Ленивый квантификатор расширяется только тогда, когда что-то после него — разделитель, литерал или якорь — требует этого.
Ленивый +? в действии
C+? должен совпасть хотя бы с одним "C" (это требование +), и ничто после него не требует большего, поэтому он останавливается на первом: "ABC".
Канонический пример: <.*> против <.*?>
Разницу между жадным и ленивым режимами легче всего увидеть, когда есть что-то после квантификатора. Совпадение с HTML-тегами — хрестоматийный пример. Запустите оба шаблона против одной строки:
- Жадный
/<.*>/совпадает с"<p>Hello</p>"— всей строкой..*поглощает всё, затем откатывается ровно настолько, чтобы оставить>для финального>в шаблоне, останавливаясь на последнем>. - Ленивый
/<.*?>/совпадает только с"<p>"— одним тегом..*?расширяется посимвольно и останавливается, как только достигает первого>.
Когда цель — «совпасть с одним тегом», «совпасть с одной строкой в кавычках» или «совпасть до следующего разделителя», ленивый режим почти всегда то, что нужно.
Полное семейство ленивых квантификаторов
У каждого жадного квантификатора есть ленивый двойник, образуемый добавлением ?:
| Жадный | Ленивый | Смысл ленивой формы |
|---|---|---|
* | *? | Ноль или более, как можно меньше |
+ | +? | Один или более, как можно меньше |
? | ?? | Ноль или один, предпочитать ноль |
{2,5} | {2,5}? | От 2 до 5, предпочитать 2 |
{2,} | {2,}? | Не менее 2, предпочитать 2 |
Обратите внимание, что ?? — не опечатка: первый ? — квантификатор (ноль или один), а второй делает его ленивым, поэтому при наличии выбора он предпочитает не совпадать ни с чем.
Когда следует использовать ленивые квантификаторы?
Практическое правило:
Используйте ленивый квантификатор, когда хотите совпасть до первого вхождения разделителя, и жадный — когда нужно всё до последнего.
Типичные ситуации, где ленивый режим является правильным выбором:
- Извлечение одного HTML/XML-тега:
/<.*?>/. - Захват содержимого одной пары кавычек или скобок:
/".*?"/,/\(.*?\)/. - Получение текста до следующего разделителя без захвата следующей записи.
Два важных предостережения:
- Разделитель часто понятнее, чем ленивость. Вместо
/".*?"/можно написать/"[^"]*"/с отрицательным классом символов. Это полностью избегает отката и обычно работает быстрее и предсказуемее. - Ленивому нужна точка остановки. Как показал пример с
AB.*?, ленивый квантификатор в конце шаблона (когда ничто не толкает его вперёд) сжимается до минимума и почти ничего не совпадает. Сочетайте его со следующим литералом, якорем или захватывающей группой.
Итоги
- Квантификаторы по умолчанию жадные — они берут максимум, затем откатываются, чтобы остальная часть шаблона совпала.
- Добавьте
?, чтобы сделать квантификатор ленивым — он берёт минимум, затем расширяется только по необходимости. - Ленивый квантификатор без ничего после него совпадает как можно меньше (часто ничего), поэтому всегда давайте ему разделитель или якорь для остановки.
- Полное семейство ленивых:
*?,+?,??и{n,m}?. - Используйте ленивый режим, когда нужно «до первого» разделителя; в других случаях отрицательный класс символов зачастую является более чистым и быстрым выбором.