regexp renovations
This commit is contained in:
parent
b2f6df9d45
commit
bf85f46cf9
24 changed files with 311 additions and 132 deletions
|
@ -1,4 +1,4 @@
|
|||
# Жадные и ленивые квантификаторы [todo]
|
||||
# Жадные и ленивые квантификаторы
|
||||
|
||||
Квантификаторы -- с виду очень простая, но на самом деле очень хитрая штука.
|
||||
|
||||
|
@ -55,7 +55,7 @@ alert( str.match(reg) ); // "witch" and her "broom"
|
|||
</li>
|
||||
<li>Кавычка найдена, далее движок проверяет, есть ли соответствие для остальной части паттерна.
|
||||
|
||||
В данном случае следующий символ паттерна -- `.` (точка). Она обозначает "любой символ", так что следующая буква строки <code class="match">'w'</code> вполне подходит:
|
||||
В данном случае следующий символ шаблона: <code class="pattern">.</code> (точка). Она обозначает "любой символ", так что следующая буква строки <code class="match">'w'</code> вполне подходит:
|
||||
<img src="witch_greedy2.svg">
|
||||
</li>
|
||||
<li>Далее "любой символ" повторяется, так как стоит квантификатор <code class="pattern">.+</code>. Движок регулярных выражений берёт один символ за другим, до тех пор, пока у него это получается.
|
||||
|
@ -63,32 +63,40 @@ alert( str.match(reg) ); // "witch" and her "broom"
|
|||
В данном случае это означает "до конца строки":
|
||||
<img src="witch_greedy3.svg">
|
||||
</li>
|
||||
<li>Итак, текст закончился, движок регулярных выражений больше не может найти "любой символ", он закончил строить соответствие для <code class="pattern">.+</code> и очень рад по этому поводу.
|
||||
<li>Итак, текст закончился, движок регулярных выражений больше не может найти "любой символ", он закончил повторения для <code class="pattern">.+</code> и переходит к следующему символу шаблона.
|
||||
|
||||
Следующий символ шаблона -- это кавычка. Её тоже необходимо найти, чтобы соответствие было полным. А тут -- беда, ведь поисковый текст завершился!
|
||||
|
||||
Движок регулярных выражений понимает, что, наверное, взял многовато <code class="pattern">.+</code> и начинает отступать обратно ("фаза бэктрекинга" -- backtracking на англ.).
|
||||
Движок регулярных выражений понимает, что, наверное, взял многовато <code class="pattern">.+</code> и начинает отступать обратно.
|
||||
|
||||
Иными словами, он сокращает текущее совпадение на один символ:
|
||||
|
||||
<img src="witch_greedy4.png">
|
||||
<img src="witch_greedy4.svg">
|
||||
|
||||
После этого он ещё раз пытается подобрать соответствие для остатка паттерна. Но кавычка <code class="pattern">'"'</code> не совпадает с <code class="subject">'e'</code>.</li>
|
||||
<li>...Так что движок уменьшает число повторений <code class="pattern">.+</code> ещё раз:
|
||||
Это называется "фаза возврата" или "фаза бэктрекинга" (backtracking -- англ.).
|
||||
|
||||
<img src="witch_greedy5.png">
|
||||
Теперь <code class="pattern">.+</code> соответствует почти вся оставшаяся строка, за исключением одного символа, и движок регулярных выражений ещё раз пытается подобрать соответствие для остатка шаблона, начиная с оставшейся части строки.
|
||||
|
||||
Если бы последним символом строки была кавычка <code class="pattern">'"'</code>, то на этом бы всё и закончилось. Но последний символ <code class="subject">'e'</code>, так что совпадения нет.</li>
|
||||
<li>...Поэтому движок уменьшает число повторений <code class="pattern">.+</code> ещё на один символ:
|
||||
|
||||
<img src="witch_greedy5.svg">
|
||||
|
||||
Кавычка <code class="pattern">'"'</code> не совпадает с <code class="subject">'n'</code>. Опять неудача.</li>
|
||||
<li>Движок продолжает отступать, он уменьшает количество повторений точки <code class="pattern">'.'</code> до тех пор, пока остаток паттерна не совпадёт:
|
||||
<li>Движок продолжает отступать, он уменьшает количество повторений точки <code class="pattern">'.'</code> до тех пор, пока остаток паттерна, то есть в данном случае кавычка <code class="pattern">'"'</code>, не совпадёт:
|
||||
|
||||
<img src="witch_greedy6.png">
|
||||
<img src="witch_greedy6.svg">
|
||||
</li>
|
||||
<li>Мы получили результат. Так как у регэкспа есть флаг `g`, то поиск продолжится, однако это произойдёт после первого совпадения и не даст новых результатов.</li>
|
||||
<li>Совпадение получено. Дальнейший поиск по оставшейся части строки <code class="subject">is one</code> новых совпадений не даст.</li>
|
||||
</ol>
|
||||
|
||||
Возможно, это не совсем то, что мы ожидали.
|
||||
|
||||
**В жадном режиме (по умолчанию) регэксп повторяет квантификатор настолько много раз, насколько это возможно, чтобы найти соответствие.**
|
||||
|
||||
Возможно, это не совсем то, что мы хотели, но так это работает.
|
||||
То есть, любой символ <code class="pattern">.+</code> повторился максимальное количество раз, что и привело к такой длинной строке.
|
||||
|
||||
А мы, наверное, хотели, чтобы каждая строка в кавычках была независимым совпадением? Для этого можно переключить квантификатор `+` в "ленивый" режим, о котором будет речь далее.
|
||||
|
||||
## Ленивый режим
|
||||
|
||||
|
@ -109,40 +117,38 @@ var str = 'a "witch" and her "broom" is one';
|
|||
alert( str.match(reg) ); // witch, broom
|
||||
```
|
||||
|
||||
Чтобы в точности понять, что происходим, разберём в деталях, как ищется <code class="pattern">".+?"</code>.
|
||||
Чтобы в точности понять, как поменялась работа квантификатора, разберём поиск по шагам.
|
||||
|
||||
<ol>
|
||||
<li>Первый шаг -- тот же, кавычка <code class="pattern">'"'</code> найдена на 3й позиции:
|
||||
<img src="witch_greedy1.png">
|
||||
<img src="witch_greedy1.svg">
|
||||
</li>
|
||||
|
||||
<li>Второй шаг -- тот же, находим произвольный символ <code class="pattern">'.'</code>:
|
||||
<img src="witch_greedy2.png">
|
||||
<img src="witch_greedy2.svg">
|
||||
</li>
|
||||
|
||||
<li>А вот дальше -- так как стоит ленивый режим работы `+`, то движок пытается повторять точку (произвольный символ) *минимальное количество раз*.
|
||||
<li>А вот дальше -- так как стоит ленивый режим работы `+`, то движок не повторет точку (произвольный символ) ещё раз, а останавливается на достигнутом и пытается проверить, есть ли соответствие остальной части шаблона, то есть <code class="pattern">'"'</code>:
|
||||
<img src="witch_lazy3.svg">
|
||||
|
||||
Так что он тут же пытается проверить, достаточно ли повторить 1 раз -- и для этого пытается найти соответствие остальной части шаблона, то есть <code class="pattern">'"'</code>:
|
||||
<img src="witch_lazy3.png">
|
||||
|
||||
Нет, один раз повторить недостаточно. В данном случае, символ `'i' != '"'`, но если бы оставшаяся часть паттерна была бы более сложной -- алгоритм остался бы тем же. Если остаток шаблона не находится -- увеличиваем количество повторений.
|
||||
Если бы остальная часть шаблона на данной позиции совпала, то совпадение было бы найдено. Но в данном случе -- нет, символ `'i'` не равен '"'.
|
||||
</li>
|
||||
<li>Движок регулярных выражений увиличивает количество повторений точки на одно и пытается найти соответствие остатку шаблона ещё раз:
|
||||
|
||||
<img src="witch_lazy4.png">
|
||||
<img src="witch_lazy4.svg">
|
||||
Опять неудача. Тогда поисковой движок увеличивает количество повторений ещё и ещё...
|
||||
</li>
|
||||
<li>Только на 5м шаге поисковой движок наконец находит соответствие для остатка паттерна:
|
||||
|
||||
<img src="witch_lazy5.png">
|
||||
<img src="witch_lazy5.svg">
|
||||
</li>
|
||||
<li>Так как поиск происходит с флагом `g`, то он продолжается с конца текущего совпадения, давая ещё один результат:
|
||||
|
||||
<img src="witch_lazy6.png">
|
||||
<img src="witch_lazy6.svg">
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
В примере выше продемонстрирована работа ленивого режима для <code class="pattern">+?</code>. Квантификаторы <code class="pattern">+?</code> и <code class="pattern">??</code> ведут себя аналогично -- "ленивый" движок увеличивает количество повторений только в том случае, если для остальной части шаблона на данной позиции нет соответствия, в то время как жадный сначала берёт столько повторений, сколько возможно, а потом отступает назад.
|
||||
В примере выше продемонстрирована работа ленивого режима для <code class="pattern">+?</code>. Квантификаторы <code class="pattern">+?</code> и <code class="pattern">??</code> ведут себя аналогично -- "ленивый" движок увеличивает количество повторений только в том случае, если для остальной части шаблона на данной позиции нет соответствия.
|
||||
|
||||
**Ленивость распространяется только на тот квантификатор, после которого стоит `?`.**
|
||||
|
||||
|
@ -156,14 +162,17 @@ alert( "123 456".match(/\d+ \d+?/g) ); // 123 4
|
|||
```
|
||||
|
||||
<ol>
|
||||
<li>Подпаттерн <code class="pattern">\d+</code> пытается найти столько символов, сколько возможно (работает жадно), так что он находит <code class="match">123</code> и останавливается, поскольку символ пробела <code class="pattern">' '</code> не подходит под <code class="pattern">\d</code>.</li>
|
||||
<li>Далее идёт пробел, и в игру вступает <code class="pattern">\d+?</code>.
|
||||
<li>Подшаблон <code class="pattern">\d+</code> пытается найти столько цифр, сколько возможно (работает жадно), так что он находит <code class="match">123</code> и останавливается, поскольку символ пробела <code class="pattern">' '</code> не подходит под <code class="pattern">\d</code>.</li>
|
||||
<li>Далее в шаблоне пробел, он совпадает.</li>
|
||||
<li>Далее в шаблоне идёт <code class="pattern">\d+?</code>.
|
||||
|
||||
Он находит один символ <code class="match">'4'</code> и пытатся проверить, есть ли совпадение с остатком шаблона (после <code class="pattern">\d+?</code>).
|
||||
Квантификатор указан в ленивом режиме, поэтому он находит одну цифру <code class="match">4</code> и пытается проверить, есть ли совпадение с остатком шаблона.
|
||||
|
||||
Здесь мы ещё раз заметим -- ленивый режим без необходимости ничего не возьмёт.
|
||||
Но после <code class="pattern">\d+?</code> в шаблоне ничего нет.
|
||||
|
||||
Так как шаблон закончился, то поиск завершается и <code class="match">123 4</code> становится результатом.</li>
|
||||
**Ленивый режим без необходимости лишний раз квантификатор не повторит.**
|
||||
|
||||
Так как шаблон завершился, то искать дальше, в общем-то нечего. Получено совпадение <code class="match">123 4</code>.</li>
|
||||
<li>Следующий поиск продолжится с `5`, но ничего не найдёт.</li>
|
||||
</ol>
|
||||
|
||||
|
@ -188,7 +197,139 @@ var str = 'a "witch" and her "broom" is one';
|
|||
alert( str.match(reg) ); // witch, broom
|
||||
```
|
||||
|
||||
Регэксп <code class="pattern">"[^"]+"</code> даст правильные результаты, поскольку ищет кавычку <code class="pattern">'"'</code>, за которой идут столько не-кавычек (исключающие квадратные скобки), сколько возможно. Так что вторая кавычка автоматически прекращает повторения <code class="pattern">[^"]+</code> и позволяет найти остаток шаблона <code class="pattern">"</code>.
|
||||
Регэксп <code class="pattern">"[^"]+"</code> даст правильные результаты, поскольку ищет кавычку <code class="pattern">'"'</code>, за которой идут столько не-кавычек (исключающие квадратные скобки), сколько возможно.
|
||||
|
||||
Так что вторая кавычка автоматически прекращает повторения <code class="pattern">[^"]+</code> и позволяет найти остаток шаблона <code class="pattern">"</code>.
|
||||
|
||||
**Эта логика ни в коей мере не заменяет ленивые квантификаторы!**
|
||||
|
||||
|
||||
Она просто другая. И то и другое бывает полезно.
|
||||
|
||||
Давайте посмотрим пример, когда нужен именно такой вариант, а ленивые квантификаторы не подойдут.
|
||||
|
||||
Например, мы хотим найти в тексте ссылки вида `<a href="..." class="doc">`, с любым содержанием `href`.
|
||||
|
||||
Какое регулярное выражение для этого подойдёт?
|
||||
|
||||
Первый вариант может выглядеть так: <code class="pattern">/<a href=".*" class="doc">/g</code>.
|
||||
|
||||
Проверим его:
|
||||
```js
|
||||
//+ run
|
||||
var str = '...<a href="link" class="doc">...';
|
||||
var reg = /<a href=".*" class="doc">/g;
|
||||
|
||||
// Сработало!
|
||||
alert( str.match(reg) ); // <a href="link" class="doc">
|
||||
```
|
||||
|
||||
А если в тексте несколько ссылок?
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var str = '...<a href="link1" class="doc">... <a href="link2" class="doc">...';
|
||||
var reg = /<a href=".*" class="doc">/g;
|
||||
|
||||
// Упс! Сразу две ссылки!
|
||||
alert( str.match(reg) ); // <a href="link1" class="doc">... <a href="link2" class="doc">
|
||||
```
|
||||
|
||||
На этот раз результат неверен.
|
||||
|
||||
Жадный <code class="pattern">.*</code> взял слишком много символов.
|
||||
|
||||
Соответствие получилось таким:
|
||||
```
|
||||
<a href="....................................." class="doc">
|
||||
<a href="link1" class="doc">... <a href="link2" class="doc">
|
||||
```
|
||||
|
||||
Модифицируем шаблон -- добавим ленивость квантификатору <code class="pattern">.*?</code>:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var str = '...<a href="link1" class="doc">... <a href="link2" class="doc">...';
|
||||
var reg = /<a href=".*?" class="doc">/g;
|
||||
|
||||
// Сработало!
|
||||
alert( str.match(reg) ); // <a href="link1" class="doc">, <a href="link2" class="doc">
|
||||
```
|
||||
|
||||
Теперь всё верно, два результата:
|
||||
|
||||
```
|
||||
<a href="....." class="doc"> <a href="....." class="doc">
|
||||
<a href="link1" class="doc">... <a href="link2" class="doc">
|
||||
```
|
||||
|
||||
Почему теперь всё в порядке -- для внимательного читателя, после объяснений, данных выше в этой главе, должно быть полностью очевидно.
|
||||
|
||||
Поэтому не будем останавливаться здесь на деталях, а попробуем ещё пример:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var str = '...<a href="link1" class="wrong">... <p style="" class="doc">...';
|
||||
var reg = /<a href=".*?" class="doc">/g;
|
||||
|
||||
// Неправильное совпадение!
|
||||
alert( str.match(reg) ); // <a href="link1" class="wrong">... <p style="" class="doc">
|
||||
```
|
||||
|
||||
Совпадение -- не ссылка, а более длинный текст.
|
||||
|
||||
Получилось следующее:
|
||||
<ol>
|
||||
<li>Найдено совпадение <code class="match"><a href="</code>.</li>
|
||||
<li>Лениво ищем <code class="pattern">.*?</code>, после каждого символа проверяя, есть ли совпадение остальной части шаблона.
|
||||
|
||||
Подшаблон <code class="pattern">.*?</code> будет брать символы до тех пор, пока не найдёт <code class="match">class="doc"></code>.
|
||||
|
||||
В данном случае этот поиск закончится уже за пределами ссылки, в теге `<p>`, вообще не имеющем отношения к `<a>`.
|
||||
</li>
|
||||
<li>Получившееся совпадение:
|
||||
|
||||
```
|
||||
<a href="..................................." class="doc">
|
||||
<a href="link1" class="wrong">... <p style="" class="doc">
|
||||
```
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
Итак, ленивость нам не помогла.
|
||||
|
||||
Необходимо как-то прекратить поиск <code class="pattern">.*</code>, чтобы он не вышел за пределы кавычек.
|
||||
|
||||
Для этого мы используем более точное указание, какие символы нам подходят, а какие нет.
|
||||
|
||||
Правильный вариант: <code class="pattern">[^"]*</code>. Этот шаблон будет брать все символы до ближайшей кавычки, как раз то, что требуется.
|
||||
|
||||
Рабочий пример:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var str1 = '...<a href="link1" class="wrong">... <p style="" class="doc">...';
|
||||
var str2 = '...<a href="link1" class="doc">... <a href="link2" class="doc">...';
|
||||
var reg = /<a href="[^"]*" class="doc">/g;
|
||||
|
||||
// Работает!
|
||||
alert( str1.match(reg) ); // null, совпадений нет, и это верно
|
||||
alert( str2.match(reg) ); // <a href="link1" class="doc">, <a href="link2" class="doc">
|
||||
```
|
||||
|
||||
## Итого
|
||||
|
||||
Квантификаторы имеют два режима работы:
|
||||
<dl>
|
||||
<dt>Жадный</dt>
|
||||
<dd>Режим по умолчанию -- движок регулярных выражений повторяет его по-максимуму. Когда повторять уже нельзя, например нет больше цифр для `\d+`, он продолжает поиск с оставшейся части текста. Если совпадение найти не удалось -- отступает обратно, уменьшая количество повторений.</dd>
|
||||
<dt>Ленивый</dt>
|
||||
<dd>При указании после квантификатора символа `?` он работает в ленивом режиме. То есть, он перед каждым повторением проверяет совпадение оставшейся части шаблона на текущей позиции.</dd>
|
||||
</dl>
|
||||
|
||||
Как мы видели в примере выше, ленивый режим -- не панацея от "слишком жадного" забора символов. Альтернатива -- более аккуратно настроенный "жадный", с исключением символов. Как мы увидим далее, можно исключать не только символы, но и целые подшаблоны.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue