# Жадные и ленивые квантификаторы
Квантификаторы -- с виду очень простая, но на самом деле очень хитрая штука.
Необходимо очень хорошо понимать, как именно происходит поиск, если конечно мы хотим искать что-либо сложнее чем /\d+/
.
[cut]
Для примера рассмотрим задачу, которая часто возникает в типографике -- заменить в тексте кавычки вида `"..."` (их называют "английские кавычки") на "кавычки-ёлочки": `«...»`.
Для этого нужно сначала найти все слова в таких кавычках.
Соотверствующее регулярное выражение может выглядеть так: /".+"/g
, то есть мы ищем кавычку, после которой один или более произвольный символ, и в конце опять кавычка.
Однако, если попробовать применить его на практике, даже на таком простом случае...
```js
//+ run
var reg = /".+"/g;
var str = 'a "witch" and her "broom" is one';
alert( str.match(reg) ); // "witch" and her "broom"
```
...Мы увидим, что оно работает совсем не так, как задумано!
Вместо того, чтобы найти два совпадения "witch"
и "broom"
, оно находит одно: "witch" and her "broom"
.
Это как раз тот случай, когда *жадность* -- причина всех зол.
## Жадный поиск
Чтобы найти совпадение, движок регулярных выражений обычно использует следующий алгоритм:
".+"
.
"
.
Движок регулярных выражений пытается сопоставить её на 0й позиции в строке, но символ `a`, поэтому на 0й позиции соответствия явно нет.
Далее он переходит 1ю, 2ю позицию в исходной строке и, наконец, обнаруживает кавычку на 3й позиции:
.
(точка). Она обозначает "любой символ", так что следующая буква строки 'w'
вполне подходит:
.+
. Движок регулярных выражений берёт один символ за другим, до тех пор, пока у него это получается.
В данном случае это означает "до конца строки":
.+
и переходит к следующему символу шаблона.
Следующий символ шаблона -- это кавычка. Её тоже необходимо найти, чтобы соответствие было полным. А тут -- беда, ведь поисковый текст завершился!
Движок регулярных выражений понимает, что, наверное, взял многовато .+
и начинает отступать обратно.
Иными словами, он сокращает текущее совпадение на один символ:
.+
соответствует почти вся оставшаяся строка, за исключением одного символа, и движок регулярных выражений ещё раз пытается подобрать соответствие для остатка шаблона, начиная с оставшейся части строки.
Если бы последним символом строки была кавычка '"'
, то на этом бы всё и закончилось. Но последний символ 'e'
, так что совпадения нет..+
ещё на один символ:
'"'
не совпадает с 'n'
. Опять неудача.'.'
до тех пор, пока остаток паттерна, то есть в данном случае кавычка '"'
, не совпадёт:
is one
новых совпадений не даст..+
повторился максимальное количество раз, что и привело к такой длинной строке.
А мы, наверное, хотели, чтобы каждая строка в кавычках была независимым совпадением? Для этого можно переключить квантификатор `+` в "ленивый" режим, о котором будет речь далее.
## Ленивый режим
Ленивый режим работы квантификаторов -- противоположность жадному, он означает "повторять минимальное количество раз".
Его можно включить, если поставить знак вопроса '?'
после квантификатора, так что он станет таким: *?
или +?
или даже ??
для '?'
.
Чтобы не возникло путаницы -- важно понимать: обычно `?` сам является квантификатором (ноль или один). Но если он стоит *после другого квантификатора (или даже после себя)*, то обретает другой смысл -- в этом случае он меняет режим его работы на ленивый.
Регэксп /".+?"/g
работает, как задумано -- находит отдельно witch
и broom
:
```js
//+ run
var reg = /".+?"/g;
var str = 'a "witch" and her "broom" is one';
alert( str.match(reg) ); // witch, broom
```
Чтобы в точности понять, как поменялась работа квантификатора, разберём поиск по шагам.
'"'
найдена на 3й позиции:
'.'
:
'"'
:
+?
. Квантификаторы +?
и ??
ведут себя аналогично -- "ленивый" движок увеличивает количество повторений только в том случае, если для остальной части шаблона на данной позиции нет соответствия.
**Ленивость распространяется только на тот квантификатор, после которого стоит `?`.**
Прочие квантификаторы остаются жадными.
Например:
```js
//+ run
alert( "123 456".match(/\d+ \d+?/g) ); // 123 4
```
\d+
пытается найти столько цифр, сколько возможно (работает жадно), так что он находит 123
и останавливается, поскольку символ пробела ' '
не подходит под \d
.\d+?
.
Квантификатор указан в ленивом режиме, поэтому он находит одну цифру 4
и пытается проверить, есть ли совпадение с остатком шаблона.
Но после \d+?
в шаблоне ничего нет.
**Ленивый режим без необходимости лишний раз квантификатор не повторит.**
Так как шаблон завершился, то искать дальше, в общем-то нечего. Получено совпадение 123 4
."[^"]+"
:
```js
//+ run
var reg = /"[^"]+"/g;
var str = 'a "witch" and her "broom" is one';
alert( str.match(reg) ); // witch, broom
```
Регэксп "[^"]+"
даст правильные результаты, поскольку ищет кавычку '"'
, за которой идут столько не-кавычек (исключающие квадратные скобки), сколько возможно.
Так что вторая кавычка автоматически прекращает повторения [^"]+
и позволяет найти остаток шаблона "
.
**Эта логика ни в коей мере не заменяет ленивые квантификаторы!**
Она просто другая. И то и другое бывает полезно.
Давайте посмотрим пример, когда нужен именно такой вариант, а ленивые квантификаторы не подойдут.
Например, мы хотим найти в тексте ссылки вида ``, с любым содержанием `href`.
Какое регулярное выражение для этого подойдёт?
Первый вариант может выглядеть так: /<a href=".*" class="doc">/g
.
Проверим его:
```js
//+ run
var str = '......';
var reg = //g;
// Сработало!
alert( str.match(reg) ); //
```
А если в тексте несколько ссылок?
```js
//+ run
var str = '...... ...';
var reg = //g;
// Упс! Сразу две ссылки!
alert( str.match(reg) ); // ...
```
На этот раз результат неверен.
Жадный .*
взял слишком много символов.
Соответствие получилось таким:
```
...
```
Модифицируем шаблон -- добавим ленивость квантификатору .*?
:
```js
//+ run
var str = '...... ...';
var reg = //g;
// Сработало!
alert( str.match(reg) ); // ,
```
Теперь всё верно, два результата:
```
...
```
Почему теперь всё в порядке -- для внимательного читателя, после объяснений, данных выше в этой главе, должно быть полностью очевидно.
Поэтому не будем останавливаться здесь на деталях, а попробуем ещё пример:
```js
//+ run
var str = '...... ...';
var reg = //g;
// Неправильное совпадение!
alert( str.match(reg) ); // ...
```
Совпадение -- не ссылка, а более длинный текст.
Получилось следующее:
`, вообще не имеющем отношения к ``.
```
...';
var str2 = '...... ...';
var reg = //g;
// Работает!
alert( str1.match(reg) ); // null, совпадений нет, и это верно
alert( str2.match(reg) ); // ,
```
## Итого
Квантификаторы имеют два режима работы:
Итак, ленивость нам не помогла.
Необходимо как-то прекратить поиск <a href="
..*?
, после каждого символа проверяя, есть ли совпадение остальной части шаблона.
Подшаблон .*?
будет брать символы до тех пор, пока не найдёт class="doc">
.
В данном случае этот поиск закончится уже за пределами ссылки, в теге `.*
, чтобы он не вышел за пределы кавычек.
Для этого мы используем более точное указание, какие символы нам подходят, а какие нет.
Правильный вариант: [^"]*
. Этот шаблон будет брать все символы до ближайшей кавычки, как раз то, что требуется.
Рабочий пример:
```js
//+ run
var str1 = '......
Как мы видели в примере выше, ленивый режим -- не панацея от "слишком жадного" забора символов. Альтернатива -- более аккуратно настроенный "жадный", с исключением символов. Как мы увидим далее, можно исключать не только символы, но и целые подшаблоны.