ok
|
@ -48,7 +48,7 @@ alert( result.input ); // "Fame is the thirst of youth" (the string)
|
|||
|
||||
The array may have more than one element.
|
||||
|
||||
**If a part of the pattern is delimited by brackets `(...)`, then it becomes a separate element of the array.**
|
||||
**If a part of the pattern is delimited by parentheses `(...)`, then it becomes a separate element of the array.**
|
||||
|
||||
For instance:
|
||||
|
||||
|
@ -58,18 +58,18 @@ lar str = "JavaScript is a programming language";
|
|||
let result = str.match( *!*/JAVA(SCRIPT)/i*/!* );
|
||||
|
||||
alert( result[0] ); // JavaScript (the whole match)
|
||||
alert( result[1] ); // script (the part of the match that corresponds to the brackets)
|
||||
alert( result[1] ); // script (the part of the match that corresponds to the parentheses)
|
||||
alert( result.index ); // 0
|
||||
alert( result.input ); // JavaScript is a programming language
|
||||
```
|
||||
|
||||
Due to the `i` flag the search is case-insensitive, so it finds `match:JavaScript`. The part of the match that corresponds to `pattern:SCRIPT` becomes a separate array item.
|
||||
|
||||
We'll be back to brackets later in the chapter [todo]. They are great for search-and-replace.
|
||||
We'll be back to parentheses later in the chapter <info:regexp-groups>. They are great for search-and-replace.
|
||||
|
||||
## str.match(reg) with "g" flag
|
||||
|
||||
When there's a `"g"` flag, then `str.match` returns an array of all matches. There are no additional properties in that array, and brackets do not create any elements.
|
||||
When there's a `"g"` flag, then `str.match` returns an array of all matches. There are no additional properties in that array, and parentheses do not create any elements.
|
||||
|
||||
For instance:
|
||||
|
||||
|
@ -81,9 +81,7 @@ let result = str.match( *!*/ho/ig*/!* );
|
|||
alert( result ); // HO, Ho, ho (all matches, case-insensitive)
|
||||
```
|
||||
|
||||
With brackets nothing changes, here we go:
|
||||
|
||||
|
||||
With parentheses nothing changes, here we go:
|
||||
|
||||
```js run
|
||||
let str = "HO-Ho-ho!";
|
||||
|
@ -95,7 +93,7 @@ alert( result ); // HO, Ho, ho
|
|||
|
||||
So, with `g` flag the `result` is a simple array of matches. No additional properties.
|
||||
|
||||
If we want to get information about match positions and use brackets then we should use [RegExp#exec](mdn:js/RegExp/exec) method that we'll cover below.
|
||||
If we want to get information about match positions and use parentheses then we should use [RegExp#exec](mdn:js/RegExp/exec) method that we'll cover below.
|
||||
|
||||
````warn header="If there are no matches, the call to `match` returns `null`"
|
||||
Please note, that's important. If there were no matches, the result is not an empty array, but `null`.
|
||||
|
@ -155,7 +153,7 @@ We can use special characters in it:
|
|||
|`$&`|the whole match|
|
||||
|<code>$`</code>|a part of the string before the match|
|
||||
|`$'`|a part of the string after the match|
|
||||
|`$n`|if `n` is a 1-2 digit number, then it means the contents of n-th brackets counting fro left to right|
|
||||
|`$n`|if `n` is a 1-2 digit number, then it means the contents of n-th parentheses counting fro left to right|
|
||||
|
||||
For instance let's use `$&` to replace all entries of `"John"` by `"Mr.John"`:
|
||||
|
||||
|
@ -167,7 +165,7 @@ alert(str.replace(/John/g, 'Mr.$&'));
|
|||
// "Mr.John Doe, Mr.John Smith and Mr.John Bull.";
|
||||
```
|
||||
|
||||
Brackets are very often used together with `$1`, `$2`, like this:
|
||||
Parentheses are very often used together with `$1`, `$2`, like this:
|
||||
|
||||
```js run
|
||||
let str = "John Smith";
|
||||
|
@ -195,11 +193,11 @@ In the example above the function just returns the next number every time, but u
|
|||
The function is called with arguments `func(str, p1, p2, ..., pn, offset, s)`:
|
||||
|
||||
1. `str` -- the match,
|
||||
2. `p1, p2, ..., pn` -- contents of brackets (if there are any),
|
||||
2. `p1, p2, ..., pn` -- contents of parentheses (if there are any),
|
||||
3. `offset` -- position of the match,
|
||||
4. `s` -- the source string.
|
||||
|
||||
If there are no brackets in the regexp, then the function always has 3 arguments: `func(str, offset, s)`.
|
||||
If there are no parentheses in the regexp, then the function always has 3 arguments: `func(str, offset, s)`.
|
||||
|
||||
Let's use it to show full information about matches:
|
||||
|
||||
|
@ -219,11 +217,11 @@ alert( 'Result: ' + result ); // Result: ho-ho-ho
|
|||
// Found ho at position 6 in string HO-Ho-ho
|
||||
```
|
||||
|
||||
In the example below there are two brackets, so `replacer` is called with 5 arguments: `str` is the full match, then brackets, and then `offset` and `s`:
|
||||
In the example below there are two parentheses, so `replacer` is called with 5 arguments: `str` is the full match, then parentheses, and then `offset` and `s`:
|
||||
|
||||
```js run
|
||||
function replacer(str, name, surname, offset, s) {
|
||||
// name is the first bracket, surname is the second one
|
||||
// name is the first parentheses, surname is the second one
|
||||
return surname + ", " + name;
|
||||
}
|
||||
|
||||
|
@ -264,10 +262,10 @@ alert( str.search(*!*/love/i*/!*) != -1 ); // false
|
|||
We've already seen these searching methods:
|
||||
|
||||
- `search` -- looks for the position of the match,
|
||||
- `match` -- if there's no `g` flag, returns the first match with brackets,
|
||||
- `match` -- if there's a `g` flag -- returns all matches, without separating brackets.
|
||||
- `match` -- if there's no `g` flag, returns the first match with parentheses,
|
||||
- `match` -- if there's a `g` flag -- returns all matches, without separating parentheses.
|
||||
|
||||
The `regexp.exec` method is a bit harder to use, but it allows to search all matches with brackets and positions.
|
||||
The `regexp.exec` method is a bit harder to use, but it allows to search all matches with parentheses and positions.
|
||||
|
||||
It behaves differently depending on whether the regexp has the `g` flag.
|
||||
|
||||
|
@ -276,7 +274,7 @@ It behaves differently depending on whether the regexp has the `g` flag.
|
|||
|
||||
As we can see, the method gives us nothing new if we use it without the `g` flag, because `str.match` does exactly the same.
|
||||
|
||||
But the `g` flag allows to get all matches with their positions and bracket groups.
|
||||
But the `g` flag allows to get all matches with their positions and parentheses groups.
|
||||
|
||||
Here's the example how subsequent `regexp.exec` calls return matches one by one:
|
||||
|
||||
|
@ -316,7 +314,7 @@ alert( matchThree ); // null (no match)
|
|||
alert( regexp.lastIndex ); // 0 (reset)
|
||||
```
|
||||
|
||||
As we can see, each `regexp.exec` call returns the match in a "full format": as an array with brackets, `index` and `input` properties.
|
||||
As we can see, each `regexp.exec` call returns the match in a "full format": as an array with parentheses, `index` and `input` properties.
|
||||
|
||||
The main use case for `regexp.exec` is to find all matches in a loop:
|
||||
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
Ответ: `pattern:\d\d[-:]\d\d`.
|
||||
|
||||
```js run
|
||||
var re = /\d\d[-:]\d\d/g;
|
||||
alert( "Завтрак в 09:00. Обед - в 21-30".match(re) );
|
||||
```
|
||||
|
||||
Обратим внимание, что дефис `pattern:'-'` не экранирован, поскольку в начале скобок он не может иметь специального смысла.
|
|
@ -1,11 +0,0 @@
|
|||
# Найдите время в одном из форматов
|
||||
|
||||
Время может быть в формате `часы:минуты` или `часы-минуты`. И часы и минуты состоят из двух цифр, например `09:00`, `21-30`.
|
||||
|
||||
Напишите регулярное выражение для поиска времени:
|
||||
|
||||
```js
|
||||
var re = /ваше выражение/;
|
||||
alert( "Завтрак в 09:00. Обед - в 21-30".match(re) ); // 09:00, 21-30
|
||||
```
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
# Special characters
|
||||
# Escaping, special characters
|
||||
|
||||
As we've seen, a backslash `"\"` is used to denote character classes. So it's a special character.
|
||||
|
||||
|
@ -21,7 +21,7 @@ For instance, we need to find a dot `pattern:'.'`. In a regular expression a dot
|
|||
alert( "Chapter 5.1".match(/\d\.\d/) ); // 5.1
|
||||
```
|
||||
|
||||
Brackets are also special characters, so if we want them, we should use `pattern:\(`. The example below looks for a string `"g()"`:
|
||||
Parentheses are also special characters, so if we want them, we should use `pattern:\(`. The example below looks for a string `"g()"`:
|
||||
|
||||
```js run
|
||||
alert( "function g()".match(/g\(\)/) ); // "g()"
|
||||
|
@ -46,5 +46,46 @@ alert( "/".match(/\//) ); // '/'
|
|||
From the other hand, the alternative `new RegExp` syntaxes does not require escaping it:
|
||||
|
||||
```js run
|
||||
alert( "/".match(new RegExp()/\//) ); // '/'
|
||||
alert( "/".match(new RegExp("/")) ); // '/'
|
||||
```
|
||||
|
||||
## new RegExp
|
||||
|
||||
If we are creating a regular expression with `new RegExp`, then we need to do some more escaping.
|
||||
|
||||
For instance, consider this:
|
||||
|
||||
```js run
|
||||
let reg = new RegExp("\d\.\d");
|
||||
|
||||
alert( "Chapter 5.1".match(reg) ); // null
|
||||
```
|
||||
|
||||
It doesn't work, but why?
|
||||
|
||||
The reason is string escaping rules. Look here:
|
||||
|
||||
```js run
|
||||
alert("\d\.\d"); // d.d
|
||||
```
|
||||
|
||||
Backslashes are used for escaping inside a string and string-specific special characters like `\n`. The quotes "consume" and interpret them, for instance:
|
||||
|
||||
- `\n` -- becomes a newline character,
|
||||
- `\u1234` -- becomes the Unicode character with such code,
|
||||
- ...And when there's no special meaning: like `\d` or `\z`, then the backslash is simply removed.
|
||||
|
||||
So the call to `new RegExp` gets a string without backslashes.
|
||||
|
||||
To fix it, we need to double backslashes, because quotes turn `\\` into `\`:
|
||||
|
||||
```js run
|
||||
*!*
|
||||
let regStr = "\\d\\.\\d";
|
||||
*/!*
|
||||
alert(regStr); // \d\.\d (correct now)
|
||||
|
||||
let reg = new RegExp(regStr);
|
||||
|
||||
alert( "Chapter 5.1".match(reg) ); // 5.1
|
||||
```
|
|
@ -0,0 +1,8 @@
|
|||
Answer: `pattern:\d\d[-:]\d\d`.
|
||||
|
||||
```js run
|
||||
let reg = /\d\d[-:]\d\d/g;
|
||||
alert( "Breakfast at 09:00. Dinner at 21-30".match(reg) ); // 09:00, 21-30
|
||||
```
|
||||
|
||||
Please note that the dash `pattern:'-'` has a special meaning in square brackets, but only between other characters, not when it's in the beginning or at the end, so we don't need to escape it.
|
|
@ -0,0 +1,12 @@
|
|||
# Find the time as hh:mm or hh-mm
|
||||
|
||||
The time can be in the format `hours:minutes` or `hours-minutes`. Both hours and minutes have 2 digits: `09:00` or `21-30`.
|
||||
|
||||
Write a regexp to find time:
|
||||
|
||||
```js
|
||||
let reg = /your regexp/g;
|
||||
alert( "Breakfast at 09:00. Dinner at 21-30".match(reg) ); // 09:00, 21-30
|
||||
```
|
||||
|
||||
P.S. In this task we assume that the time is always correct, there's no need to filter out bad strings like "45:67". Later we'll deal with that too.
|
|
@ -88,7 +88,7 @@ In square brackets the vast majority of special characters can be used without e
|
|||
|
||||
- A dot `pattern:'.'`.
|
||||
- A plus `pattern:'+'`.
|
||||
- Brackets `pattern:'( )'`.
|
||||
- Parentheses `pattern:'( )'`.
|
||||
- Dash `pattern:'-'` in the beginning or the end (where it does not define a range).
|
||||
- A caret `pattern:'^'` if not in the beginning (where it means exclusion).
|
||||
- And the opening square bracket `pattern:'['`.
|
|
@ -1,32 +0,0 @@
|
|||
Итак, нужно написать выражение для описания цвета, который начинается с "#", за которым следуют 6 шестнадцатеричных символов.
|
||||
|
||||
Шестнадцатеричный символ можно описать с помощью `pattern:[0-9a-fA-F]`. Мы можем сократить выражение, используя не чувствительный к регистру шаблон `pattern:[0-9a-f]`.
|
||||
|
||||
Для его шестикратного повторения мы будем использовать квантификатор `pattern:{6}`.
|
||||
|
||||
В итоге, получаем выражение вида `pattern:/#[a-f0-9]{6}/gi`.
|
||||
|
||||
```js run
|
||||
var re = /#[a-f0-9]{6}/gi;
|
||||
|
||||
var str = "color:#121212; background-color:#AA00ef bad-colors:f#fddee #fd2";
|
||||
|
||||
alert( str.match(re) ); // #121212,#AA00ef
|
||||
```
|
||||
|
||||
Проблема этого выражения в том, что оно находит цвет и в более длинных последовательностях:
|
||||
|
||||
```js run
|
||||
alert( "#12345678".match( /#[a-f0-9]{6}/gi ) ) // #12345678
|
||||
```
|
||||
|
||||
Чтобы такого не было, можно добавить в конец `\b`:
|
||||
|
||||
```js run
|
||||
// цвет
|
||||
alert( "#123456".match( /#[a-f0-9]{6}\b/gi ) ); // #123456
|
||||
|
||||
// не цвет
|
||||
alert( "#12345678".match( /#[a-f0-9]{6}\b/gi ) ); // null
|
||||
```
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
# Регулярное выражение для цвета
|
||||
|
||||
Напишите регулярное выражение для поиска HTML-цвета, заданного как `#ABCDEF`, то есть `#` и содержит затем 6 шестнадцатеричных символов.
|
||||
|
||||
Пример использования:
|
||||
|
||||
```
|
||||
var re = /*...ваше регулярное выражение...*/
|
||||
|
||||
var str = "color:#121212; background-color:#AA00ef bad-colors:f#fddee #fd2"
|
||||
|
||||
alert( str.match(re) ) // #121212,#AA00ef
|
||||
```
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
|
||||
|
||||
Целое число -- это `pattern:\d+`.
|
||||
|
||||
Десятичная точка с дробной частью -- `pattern:\.\d+`.
|
||||
|
||||
Она не обязательна, так что обернём её в скобки с квантификатором `pattern:'?'`.
|
||||
|
||||
Итого, получилось регулярное выражение `pattern:\d+(\.\d+)?`:
|
||||
|
||||
```js run
|
||||
var re = /\d+(\.\d+)?/g
|
||||
|
||||
var str = "1.5 0 12. 123.4.";
|
||||
|
||||
alert( str.match(re) ); // 1.5, 0, 12, 123.4
|
||||
```
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
# Найдите положительные числа
|
||||
|
||||
Создайте регэксп, который ищет все положительные числа, в том числе и с десятичной точкой.
|
||||
|
||||
Пример использования:
|
||||
|
||||
```js
|
||||
var re = /* ваш регэксп */
|
||||
|
||||
var str = "1.5 0 12. 123.4.";
|
||||
|
||||
alert( str.match(re) ); // 1.5, 0, 12, 123.4
|
||||
```
|
|
@ -1,12 +0,0 @@
|
|||
Целое число с необязательной дробной частью -- это `pattern:\d+(\.\d+)?`.
|
||||
|
||||
К этому нужно добавить необязательный `-` в начале:
|
||||
|
||||
```js run
|
||||
var re = /-?\d+(\.\d+)?/g
|
||||
|
||||
var str = "-1.5 0 2 -123.4.";
|
||||
|
||||
alert( str.match(re) ); // -1.5, 0, 2, -123.4
|
||||
```
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
# Найдите десятичные числа
|
||||
|
||||
Создайте регэксп, который ищет все числа, в том числе и с десятичной точкой, в том числе и отрицательные.
|
||||
|
||||
Пример использования:
|
||||
|
||||
```js
|
||||
var re = /* ваш регэксп */
|
||||
|
||||
var str = "-1.5 0 2 -123.4.";
|
||||
|
||||
alert( str.match(re) ); // -1.5, 0, 2, -123.4
|
||||
```
|
|
@ -1,6 +0,0 @@
|
|||
|
||||
Результат: `123 456`.
|
||||
|
||||
Ленивый `\d+?` будет брать цифры до пробела, то есть `123`. После каждой цифры он будет останавливаться, проверять -- не пробел ли дальше? Если нет -- брать ещё цифру, в итоге возьмёт `123`.
|
||||
|
||||
Затем в дело вступит `\d+`, который по-максимуму возьмёт дальнейшие цифры, то есть `456`.
|
|
@ -1,8 +0,0 @@
|
|||
# Совпадение для /d+? d+/
|
||||
|
||||
Что будет при таком поиске, когда сначала стоит ленивый, а потом жадный квантификаторы?
|
||||
|
||||
```js
|
||||
"123 456".match(/\d+? \d+/g) ); // какой результат?
|
||||
```
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
Они очень похожи и, да, *почти* одинаковы. Оба ищут от одной кавычки до другой.
|
||||
|
||||
Различие здесь в символе точка `pattern:'.'`. Как мы помним, точка `pattern:'.'` обозначает *любой символ, кроме перевода строки*.
|
||||
|
||||
А `pattern:[^"]` -- это *любой символ, кроме кавычки `pattern:'"'`.
|
||||
|
||||
Получается, что первый регэксп `pattern:"[^"]*"` найдёт закавыченные строки с `\n` внутри, а второй регэксп `pattern:".*?"` -- нет.
|
||||
|
||||
Вот пример:
|
||||
```js run
|
||||
alert( '"многострочный \n текст"'.match(/"[^"]*"/) ); // найдёт
|
||||
|
||||
alert( '"многострочный \n текст"'.match(/".*?"/) ); // null (нет совпадений)
|
||||
```
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
# Различие между "[^"]*" и ".*?"
|
||||
|
||||
Регулярные выражения `pattern:"[^"]*"` и `pattern:".*?"` -- при выполнении одинаковы?
|
||||
|
||||
Иначе говоря, существует ли такая строка, на которой они дадут разные результаты? Если да -- дайте такую строку.
|
|
@ -1,17 +0,0 @@
|
|||
Нужно найти начало комментария `match:<!--`, затем всё до конца `match:-->`.
|
||||
|
||||
С первого взгляда кажется, что это сделает регулярное выражение `pattern:<!--.*?-->` -- квантификатор сделан ленивым, чтобы остановился, достигнув `match:-->`.
|
||||
|
||||
Однако, точка в JavaScript -- любой символ, *кроме* конца строки. Поэтому такой регэксп не найдёт многострочный комментарий.
|
||||
|
||||
Всё получится, если вместо точки использовать полностю "всеядный" `pattern:[\s\S]`.
|
||||
|
||||
Итого:
|
||||
|
||||
```js run
|
||||
var re = /<!--[\s\S]*?-->/g;
|
||||
|
||||
var str = '.. <!-- Мой -- комментарий \n тест --> .. <!----> .. ';
|
||||
|
||||
alert( str.match(re) ); // '<!-- Мой -- комментарий \n тест -->', '<!---->'
|
||||
```
|
|
@ -1,12 +0,0 @@
|
|||
# Найти HTML-комментарии
|
||||
|
||||
Найдите все HTML-комментарии в тексте:
|
||||
|
||||
```js
|
||||
var re = ..ваш регэксп..
|
||||
|
||||
var str = '.. <!-- Мой -- комментарий \n тест --> .. <!----> .. ';
|
||||
|
||||
alert( str.match(re) ); // '<!-- Мой -- комментарий \n тест -->', '<!---->'
|
||||
```
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
Начнём поиск с `pattern:<`, затем один или более произвольный символ, но до закрывающего "уголка": `pattern:.+?>`.
|
||||
|
||||
Проверим, как работает этот регэксп:
|
||||
|
||||
```js run
|
||||
var re = /<.+?>/g;
|
||||
|
||||
var str = '<> <a href="/"> <input type="radio" checked> <b>';
|
||||
|
||||
alert( str.match(re) ); // <> <a href="/">, <input type="radio" checked>, <b>
|
||||
```
|
||||
|
||||
Результат неверен! В качестве первого тега регэксп нашёл подстроку `match:<> <a href="/">`, но это явно не тег.
|
||||
|
||||
Всё потому, что `pattern:.+?` -- это "любой символ (кроме `\n`), повторяющийся один и более раз до того, как оставшаяся часть шаблона совпадёт (ленивость)".
|
||||
|
||||
Поэтому он находит первый `<`, затем есть "всё подряд" до следующего `>`.
|
||||
|
||||
Первое совпадение получается как раз таким:
|
||||
|
||||
```
|
||||
<.............>
|
||||
<> <a href="/"> <input type="radio" checked> <b>
|
||||
```
|
||||
|
||||
Правильным решением будет использовать `pattern:<[^>]+>`:
|
||||
|
||||
```js run
|
||||
var re = /<[^>]+>/g
|
||||
|
||||
var str = '<> <a href="/"> <input type="radio" checked> <b>';
|
||||
|
||||
alert( str.match(re) ); // <a href="/">, <input type="radio" checked>, <b>
|
||||
```
|
||||
|
||||
Это же решение автоматически позволяет находится внутри тегу символу `\n`, который в класс точка `.` не входит.
|
|
@ -1,17 +0,0 @@
|
|||
# Найти HTML-теги
|
||||
|
||||
Создайте регулярное выражение для поиска всех (открывающихся и закрывающихся) HTML-тегов вместе с атрибутами.
|
||||
|
||||
Пример использования:
|
||||
```js run
|
||||
var re = /* ваш регэксп */
|
||||
|
||||
var str = '<> <a href="/"> <input type="radio" checked> <b>';
|
||||
|
||||
alert( str.match(re) ); // '<a href="/">', '<input type="radio" checked>', '<b>'
|
||||
```
|
||||
|
||||
В этой задаче можно считать, что тег начинается с <code><</code>, заканчивается <code>></code> и может содержать внутри любые символы, кроме <code><</code> и <code>></code>.
|
||||
|
||||
Но хотя бы один символ внутри тега должен быть: <code><></code> -- не тег.
|
||||
|
|
@ -1,308 +0,0 @@
|
|||
# Жадные и ленивые квантификаторы
|
||||
|
||||
Квантификаторы -- с виду очень простая, но на самом деле очень хитрая штука.
|
||||
|
||||
Необходимо очень хорошо понимать, как именно происходит поиск, если конечно мы хотим искать что-либо сложнее чем `pattern:/\d+/`.
|
||||
|
||||
[cut]
|
||||
|
||||
Для примера рассмотрим задачу, которая часто возникает в типографике -- заменить в тексте кавычки вида `"..."` (их называют "английские кавычки") на "кавычки-ёлочки": `«...»`.
|
||||
|
||||
Для этого нужно сначала найти все слова в таких кавычках.
|
||||
|
||||
Соответствующее регулярное выражение может выглядеть так: `pattern:/".+"/g`, то есть мы ищем кавычку, после которой один или более произвольный символ, и в конце опять кавычка.
|
||||
|
||||
Однако, если попробовать применить его на практике, даже на таком простом случае...
|
||||
|
||||
```js run
|
||||
var reg = /".+"/g;
|
||||
|
||||
var str = 'a "witch" and her "broom" is one';
|
||||
|
||||
alert( str.match(reg) ); // "witch" and her "broom"
|
||||
```
|
||||
|
||||
...Мы увидим, что оно работает совсем не так, как задумано!
|
||||
|
||||
Вместо того, чтобы найти два совпадения `match:"witch"` и `match:"broom"`, оно находит одно: `match:"witch" and her "broom"`.
|
||||
|
||||
Это как раз тот случай, когда *жадность* -- причина всех зол.
|
||||
|
||||
## Жадный поиск
|
||||
|
||||
Чтобы найти совпадение, движок регулярных выражений обычно использует следующий алгоритм:
|
||||
|
||||
- Для каждой позиции в поисковой строке
|
||||
- Проверить совпадение на данной позиции
|
||||
- Посимвольно, с учётом классов и квантификаторов сопоставив с ней регулярное выражение.
|
||||
|
||||
Это общие слова, гораздо понятнее будет, если мы проследим, что именно он делает для регэкспа `pattern:".+"`.
|
||||
|
||||
1. Первый символ шаблона -- это кавычка `pattern:"`.
|
||||
|
||||
Движок регулярных выражений пытается сопоставить её на 0-й позиции в строке, но символ `a`, поэтому на 0-й позиции соответствия явно нет.
|
||||
|
||||
Далее он переходит 1ю, 2ю позицию в исходной строке и, наконец, обнаруживает кавычку на 3-й позиции:
|
||||
|
||||

|
||||
|
||||
2. Кавычка найдена, далее движок проверяет, есть ли соответствие для остальной части паттерна.
|
||||
|
||||
В данном случае следующий символ шаблона: `pattern:.` (точка). Она обозначает "любой символ", так что следующая буква строки `match:'w'` вполне подходит:
|
||||
|
||||

|
||||
|
||||
3. Далее "любой символ" повторяется, так как стоит квантификатор `pattern:.+`. Движок регулярных выражений берёт один символ за другим, до тех пор, пока у него это получается.
|
||||
|
||||
В данном случае это означает "до конца строки":
|
||||
|
||||

|
||||
|
||||
4. Итак, текст закончился, движок регулярных выражений больше не может найти "любой символ", он закончил повторения для `pattern:.+` и переходит к следующему символу шаблона.
|
||||
|
||||
Следующий символ шаблона -- это кавычка. Её тоже необходимо найти, чтобы соответствие было полным. А тут -- беда, ведь поисковый текст завершился!
|
||||
|
||||
Движок регулярных выражений понимает, что, наверное, взял многовато `pattern:.+` и начинает отступать обратно.
|
||||
|
||||
Иными словами, он сокращает текущее совпадение на один символ:
|
||||
|
||||

|
||||
|
||||
Это называется "фаза возврата" или "фаза бэктрекинга" (backtracking -- англ.).
|
||||
|
||||
Теперь `pattern:.+` соответствует почти вся оставшаяся строка, за исключением одного символа, и движок регулярных выражений ещё раз пытается подобрать соответствие для остатка шаблона, начиная с оставшейся части строки.
|
||||
|
||||
Если бы последним символом строки была кавычка `pattern:'"'`, то на этом бы всё и закончилось. Но последний символ `subject:'e'`, так что совпадения нет.
|
||||
|
||||
5. ...Поэтому движок уменьшает число повторений `pattern:.+` ещё на один символ:
|
||||
|
||||

|
||||
|
||||
Кавычка `pattern:'"'` не совпадает с `subject:'n'`. Опять неудача.
|
||||
|
||||
6. Движок продолжает отступать, он уменьшает количество повторений точки `pattern:'.'` до тех пор, пока остаток паттерна, то есть в данном случае кавычка `pattern:'"'`, не совпадёт:
|
||||
|
||||

|
||||
|
||||
7. Совпадение получено. Дальнейший поиск по оставшейся части строки `subject:is one` новых совпадений не даст.
|
||||
|
||||
Возможно, это не совсем то, что мы ожидали.
|
||||
|
||||
**В жадном режиме (по умолчанию) регэксп повторяет квантификатор настолько много раз, насколько это возможно, чтобы найти соответствие.**
|
||||
|
||||
То есть, любой символ `pattern:.+` повторился максимальное количество раз, что и привело к такой длинной строке.
|
||||
|
||||
А мы, наверное, хотели, чтобы каждая строка в кавычках была независимым совпадением? Для этого можно переключить квантификатор `+` в "ленивый" режим, о котором будет речь далее.
|
||||
|
||||
## Ленивый режим
|
||||
|
||||
Ленивый режим работы квантификаторов -- противоположность жадному, он означает "повторять минимальное количество раз".
|
||||
|
||||
Его можно включить, если поставить знак вопроса `pattern:'?'` после квантификатора, так что он станет таким: `pattern:*?` или `pattern:+?` или даже `pattern:??` для `pattern:'?'`.
|
||||
|
||||
Чтобы не возникло путаницы -- важно понимать: обычно `?` сам является квантификатором (ноль или один). Но если он стоит *после другого квантификатора (или даже после себя)*, то обретает другой смысл -- в этом случае он меняет режим его работы на ленивый.
|
||||
|
||||
Регэксп `pattern:/".+?"/g` работает, как задумано -- находит отдельно `match:witch` и `match:broom`:
|
||||
|
||||
```js run
|
||||
var reg = /".+?"/g;
|
||||
|
||||
var str = 'a "witch" and her "broom" is one';
|
||||
|
||||
alert( str.match(reg) ); // witch, broom
|
||||
```
|
||||
|
||||
Чтобы в точности понять, как поменялась работа квантификатора, разберём поиск по шагам.
|
||||
|
||||
1. Первый шаг -- тот же, кавычка `pattern:'"'` найдена на 3-й позиции:
|
||||
|
||||

|
||||
|
||||
2. Второй шаг -- тот же, находим произвольный символ `pattern:'.'`:
|
||||
|
||||

|
||||
|
||||
3. А вот дальше -- так как стоит ленивый режим работы `+`, то движок не повторит точку (произвольный символ) ещё раз, а останавливается на достигнутом и пытается проверить, есть ли соответствие остальной части шаблона, то есть `pattern:'"'`:
|
||||
|
||||

|
||||
|
||||
Если бы остальная часть шаблона на данной позиции совпала, то совпадение было бы найдено. Но в данном случае -- нет, символ `'i'` не равен '"'.
|
||||
4. Движок регулярных выражений увиличивает количество повторений точки на одно и пытается найти соответствие остатку шаблона ещё раз:
|
||||
|
||||

|
||||
Опять неудача. Тогда поисковой движок увеличивает количество повторений ещё и ещё...
|
||||
5. Только на пятом шаге поисковой движок наконец находит соответствие для остатка паттерна:
|
||||
|
||||

|
||||
6. Так как поиск происходит с флагом `g`, то он продолжается с конца текущего совпадения, давая ещё один результат:
|
||||
|
||||

|
||||
|
||||
В примере выше продемонстрирована работа ленивого режима для `pattern:+?`. Квантификаторы `pattern:+?` и `pattern:??` ведут себя аналогично -- "ленивый" движок увеличивает количество повторений только в том случае, если для остальной части шаблона на данной позиции нет соответствия.
|
||||
|
||||
**Ленивость распространяется только на тот квантификатор, после которого стоит `?`.**
|
||||
|
||||
Прочие квантификаторы остаются жадными.
|
||||
|
||||
Например:
|
||||
|
||||
```js run
|
||||
alert( "123 456".match(/\d+ \d+?/g) ); // 123 4
|
||||
```
|
||||
|
||||
1. Подшаблон `pattern:\d+` пытается найти столько цифр, сколько возможно (работает жадно), так что он находит `match:123` и останавливается, поскольку символ пробела `pattern:' '` не подходит под `pattern:\d`.
|
||||
2. Далее в шаблоне пробел, он совпадает.
|
||||
3. Далее в шаблоне идёт `pattern:\d+?`.
|
||||
|
||||
Квантификатор указан в ленивом режиме, поэтому он находит одну цифру `match:4` и пытается проверить, есть ли совпадение с остатком шаблона.
|
||||
|
||||
Но после `pattern:\d+?` в шаблоне ничего нет.
|
||||
|
||||
**Ленивый режим без необходимости лишний раз квантификатор не повторит.**
|
||||
|
||||
Так как шаблон завершился, то искать дальше, в общем-то нечего. Получено совпадение `match:123 4`.
|
||||
4. Следующий поиск продолжится с `5`, но ничего не найдёт.
|
||||
|
||||
```smart header="Конечные автоматы и не только"
|
||||
Современные движки регулярных выражений могут иметь более хитрую реализацию внутренних алгоритмов, чтобы искать быстрее.
|
||||
|
||||
Однако, чтобы понять, как работает регулярное выражение, и строить регулярные выражения самому, знание этих хитрых алгоритмов ни к чему. Они служат лишь внутренней оптимизации способа поиска, описанного выше.
|
||||
|
||||
Кроме того, сложные регулярные выражения плохо поддаются всяким оптимизациям, так что поиск вполне может работать и в точности как здесь описано.
|
||||
```
|
||||
|
||||
## Альтернативный подход
|
||||
|
||||
В данном конкретном случае, возможно искать строки в кавычках, оставаясь в жадном режиме, с использованием регулярного выражения `pattern:"[^"]+"`:
|
||||
|
||||
```js run
|
||||
var reg = /"[^"]+"/g;
|
||||
|
||||
var str = 'a "witch" and her "broom" is one';
|
||||
|
||||
alert( str.match(reg) ); // witch, broom
|
||||
```
|
||||
|
||||
Регэксп `pattern:"[^"]+"` даст правильные результаты, поскольку ищет кавычку `pattern:'"'`, за которой идут столько не-кавычек (исключающие квадратные скобки), сколько возможно.
|
||||
|
||||
Так что вторая кавычка автоматически прекращает повторения `pattern:[^"]+` и позволяет найти остаток шаблона `pattern:"`.
|
||||
|
||||
**Эта логика ни в коей мере не заменяет ленивые квантификаторы!**
|
||||
|
||||
Она просто другая. И то и другое бывает полезно.
|
||||
|
||||
Давайте посмотрим пример, когда нужен именно такой вариант, а ленивые квантификаторы не подойдут.
|
||||
|
||||
Например, мы хотим найти в тексте ссылки вида `<a href="..." class="doc">`, с любым содержанием `href`.
|
||||
|
||||
Какое регулярное выражение для этого подойдёт?
|
||||
|
||||
Первый вариант может выглядеть так: `pattern:/<a href=".*" class="doc">/g`.
|
||||
|
||||
Проверим его:
|
||||
```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">
|
||||
```
|
||||
|
||||
На этот раз результат неверен.
|
||||
|
||||
Жадный `pattern:.*` взял слишком много символов.
|
||||
|
||||
Соответствие получилось таким:
|
||||
```
|
||||
<a href="....................................." class="doc">
|
||||
<a href="link1" class="doc">... <a href="link2" class="doc">
|
||||
```
|
||||
|
||||
Модифицируем шаблон -- добавим ленивость квантификатору `pattern:.*?`:
|
||||
|
||||
```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">
|
||||
```
|
||||
|
||||
Совпадение -- не ссылка, а более длинный текст.
|
||||
|
||||
Получилось следующее:
|
||||
|
||||
1. Найдено совпадение `match:<a href="`.
|
||||
2. Лениво ищем `pattern:.*?`, после каждого символа проверяя, есть ли совпадение остальной части шаблона.
|
||||
|
||||
Подшаблон `pattern:.*?` будет брать символы до тех пор, пока не найдёт `match:class="doc">`.
|
||||
|
||||
В данном случае этот поиск закончится уже за пределами ссылки, в теге `<p>`, вообще не имеющем отношения к `<a>`.
|
||||
3. Получившееся совпадение:
|
||||
|
||||
```
|
||||
<a href="..................................." class="doc">
|
||||
<a href="link1" class="wrong">... <p style="" class="doc">
|
||||
```
|
||||
|
||||
Итак, ленивость нам не помогла.
|
||||
|
||||
Необходимо как-то прекратить поиск `pattern:.*`, чтобы он не вышел за пределы кавычек.
|
||||
|
||||
Для этого мы используем более точное указание, какие символы нам подходят, а какие нет.
|
||||
|
||||
Правильный вариант: `pattern:[^"]*`. Этот шаблон будет брать все символы до ближайшей кавычки, как раз то, что требуется.
|
||||
|
||||
Рабочий пример:
|
||||
|
||||
```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">
|
||||
```
|
||||
|
||||
## Итого
|
||||
|
||||
Квантификаторы имеют два режима работы:
|
||||
|
||||
Жадный
|
||||
: Режим по умолчанию -- движок регулярных выражений повторяет его по-максимуму. Когда повторять уже нельзя, например нет больше цифр для `\d+`, он продолжает поиск с оставшейся части текста. Если совпадение найти не удалось -- отступает обратно, уменьшая количество повторений.
|
||||
|
||||
Ленивый
|
||||
: При указании после квантификатора символа `?` он работает в ленивом режиме. То есть, он перед каждым повторением проверяет совпадение оставшейся части шаблона на текущей позиции.
|
||||
|
||||
Как мы видели в примере выше, ленивый режим -- не панацея от "слишком жадного" забора символов. Альтернатива -- более аккуратно настроенный "жадный", с исключением символов. Как мы увидим далее, можно исключать не только символы, но и целые подшаблоны.
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
We need to look for `#` followed by 6 hexadimal characters.
|
||||
|
||||
A hexadimal character can be described as `pattern:[0-9a-fA-F]`. Or if we use the `i` flag, then just `pattern:[0-9a-f]`.
|
||||
|
||||
Then we can look for 6 of them using the quantifier `pattern:{6}`.
|
||||
|
||||
As a result, we have the regexp: `pattern:/#[a-f0-9]{6}/gi`.
|
||||
|
||||
```js run
|
||||
let reg = /#[a-f0-9]{6}/gi;
|
||||
|
||||
let str = "color:#121212; background-color:#AA00ef bad-colors:f#fddee #fd2"
|
||||
|
||||
alert( str.match(reg) ); // #121212,#AA00ef
|
||||
```
|
||||
|
||||
The problem is that it finds the color in longer sequences:
|
||||
|
||||
```js run
|
||||
alert( "#12345678".match( /#[a-f0-9]{6}/gi ) ) // #12345678
|
||||
```
|
||||
|
||||
To fix that, we can add `pattern:\b` to the end:
|
||||
|
||||
```js run
|
||||
// color
|
||||
alert( "#123456".match( /#[a-f0-9]{6}\b/gi ) ); // #123456
|
||||
|
||||
// not a color
|
||||
alert( "#12345678".match( /#[a-f0-9]{6}\b/gi ) ); // null
|
||||
```
|
|
@ -0,0 +1,15 @@
|
|||
# Regexp for HTML colors
|
||||
|
||||
Create a regexp to search HTML-colors written as `#ABCDEF`: first `#` and then 6 hexadimal characters.
|
||||
|
||||
An example of use:
|
||||
|
||||
```js
|
||||
let reg = /...your regexp.../
|
||||
|
||||
let str = "color:#121212; background-color:#AA00ef bad-colors:f#fddee #fd2 #12345678";
|
||||
|
||||
alert( str.match(reg) ) // #121212,#AA00ef
|
||||
```
|
||||
|
||||
P.S. In this task we do not need other color formats like `#123` or `rgb(1,2,3)` etc.
|
|
@ -0,0 +1,6 @@
|
|||
|
||||
The result is: `match:123 4`.
|
||||
|
||||
First the lazy `pattern:\d+?` tries to take as little digits as it can, but it has to reach the space, so it takes `match:123`.
|
||||
|
||||
Then the second `\d+?` takes only one digit, because that's enough.
|
|
@ -0,0 +1,7 @@
|
|||
# A match for /d+? d+?/
|
||||
|
||||
What's the match here?
|
||||
|
||||
```js
|
||||
"123 456".match(/\d+? \d+?/g) ); // ?
|
||||
```
|
|
@ -0,0 +1,17 @@
|
|||
We need to find the beginning of the comment `match:<!--`, then everything till the end of `match:-->`.
|
||||
|
||||
The first idea could be `pattern:<!--.*?-->` -- the lazy quantifier makes the dot stop right before `match:-->`.
|
||||
|
||||
But a dot in Javascript means "any symbol except the newline". So multiline comments won't be found.
|
||||
|
||||
We can use `pattern:[\s\S]` instead of the dot to match "anything":
|
||||
|
||||
```js run
|
||||
let reg = /<!--[\s\S]*?-->/g;
|
||||
|
||||
let str = `... <!-- My -- comment
|
||||
test --> .. <!----> ..
|
||||
`;
|
||||
|
||||
alert( str.match(reg) ); // '<!-- My -- comment \n test -->', '<!---->'
|
||||
```
|
|
@ -0,0 +1,13 @@
|
|||
# Find HTML comments
|
||||
|
||||
Find all HTML comments in the text:
|
||||
|
||||
```js
|
||||
let reg = /your regexp/g;
|
||||
|
||||
let str = `... <!-- My -- comment
|
||||
test --> .. <!----> ..
|
||||
`;
|
||||
|
||||
alert( str.match(reg) ); // '<!-- My -- comment \n test -->', '<!---->'
|
||||
```
|
|
@ -0,0 +1,10 @@
|
|||
|
||||
The solution is `pattern:<[^<>]+>`.
|
||||
|
||||
```js run
|
||||
let reg = /<[^<>]+>/g;
|
||||
|
||||
let str = '<> <a href="/"> <input type="radio" checked> <b>';
|
||||
|
||||
alert( str.match(reg) ); // '<a href="/">', '<input type="radio" checked>', '<b>'
|
||||
```
|
|
@ -0,0 +1,15 @@
|
|||
# Find HTML tags
|
||||
|
||||
Create a regular expression to find all (opening and closing) HTML tags with their attributes.
|
||||
|
||||
An example of use:
|
||||
|
||||
```js run
|
||||
let reg = /your regexp/g;
|
||||
|
||||
let str = '<> <a href="/"> <input type="radio" checked> <b>';
|
||||
|
||||
alert( str.match(reg) ); // '<a href="/">', '<input type="radio" checked>', '<b>'
|
||||
```
|
||||
|
||||
Let's assume that may not contain `<` and `>` inside (in quotes too), that simplifies things a bit.
|
|
@ -0,0 +1,308 @@
|
|||
# Greedy and lazy quantifiers
|
||||
|
||||
Quantifiers are very simple from the first sight, but in fact they can be tricky.
|
||||
|
||||
We should understand how the search works very well if we plan to look for something more complex than `pattern:/\d+/`.
|
||||
|
||||
[cut]
|
||||
|
||||
Let's take the following task as an example.
|
||||
|
||||
We have a text and need to replace all quotes `"..."` with guillemet marks: `«...»`. They are preferred for typography in many countries.
|
||||
|
||||
For instance: `"Hello, world"` should become `«Hello, world»`.
|
||||
|
||||
Some countries prefer `„Witam, świat!”` (Polish) or even `「你好,世界」` (Chinese) quotes. For different locales we can choose different replacements, but that all works the same, so let's start with `«...»`.
|
||||
|
||||
To make replacements we first need to find all quoted substrings.
|
||||
|
||||
The regular expression can look like this: `pattern:/".+"/g`. That is: we look for a quote followed by one or more characters, and then another quote.
|
||||
|
||||
...But if we try to apply it, even in such a simple case...
|
||||
|
||||
```js run
|
||||
let reg = /".+"/g;
|
||||
|
||||
let str = 'a "witch" and her "broom" is one';
|
||||
|
||||
alert( str.match(reg) ); // "witch" and her "broom"
|
||||
```
|
||||
|
||||
...We can see that it works not as intended!
|
||||
|
||||
Instead of finding two matches `match:"witch"` and `match:"broom"`, it finds one: `match:"witch" and her "broom"`.
|
||||
|
||||
That can be described as "greediness is the cause of all evil".
|
||||
|
||||
## Greedy search
|
||||
|
||||
To find a match, the regular expression engine uses the following algorithm:
|
||||
|
||||
- For every position in the string
|
||||
- Match the pattern symbol-by-symbol using classes and quantifiers.
|
||||
- If there's no match, go to the next position.
|
||||
|
||||
These common words do not make it obvious why the regexp fails, so let's elaborate how the search works for the pattern `pattern:".+"`.
|
||||
|
||||
1. The first pattern characeter is a quote `pattern:"`.
|
||||
|
||||
The regular expression engine tries to find it on 0-th position of the source string, but there's `subject:a` there, so no match.
|
||||
|
||||
Then it advances: goes to the 1st, 2nd positions in the source string and tries to find the pattern there, and finally finds the quote at the 3rd position:
|
||||
|
||||

|
||||
|
||||
2. The quote is detected, and then the engine tries to find a match for the rest of the pattern.
|
||||
|
||||
In our case the next pattern character is `pattern:.` (a dot). It denotes "any character except a newline", so the next string letter `match:'w'` fits:
|
||||
|
||||

|
||||
|
||||
3. Then the dot repeats because of the quantifier `pattern:.+`. The regular expression engine builds the match by taking characters one by one while it is possible.
|
||||
|
||||
...When it becomes impossible? All characters match the dot, so it only stops when it reaches the end of the string:
|
||||
|
||||

|
||||
|
||||
4. Now the engine finished repeating for `pattern:.+` and tries to find the next character of the pattern. It's the quote `pattern:"`. But there's a problem: the string has finished, there are no more characters!
|
||||
|
||||
The regular expression engine understands that it took too many `pattern:.+` and starts to *backtrack*.
|
||||
|
||||
In other words, it shortens the match for the quantifier by one character:
|
||||
|
||||

|
||||
|
||||
Now it assumes that `pattern:.+` ends one character before the end and tries to match the rest of the pattern from that position.
|
||||
|
||||
If there were a quote there, then that would be the end, but the last character is `subject:'e'`, so there's no match.
|
||||
|
||||
5. ...So the engine decreases the number of repetitions of `pattern:.+` by one more character:
|
||||
|
||||

|
||||
|
||||
The quote `pattern:'"'` does not match `subject:'n'`.
|
||||
|
||||
6. The engine keep backtracking: it decreases the count of repetition for `pattern:'.'` until the rest of the pattern (in our case `pattern:'"'`) matches:
|
||||
|
||||

|
||||
|
||||
7. The match is complete.
|
||||
|
||||
8. So the first match is `match:"witch" and her "broom"`. The further search starts where the first match ends, but there are no more quotes in the rest of the string `subject:is one`, so no more results.
|
||||
|
||||
That's probably not what we expected, but that's how it works.
|
||||
|
||||
**In the greedy mode (by default) the quantifier is repeated as many times as possible.**
|
||||
|
||||
The regexp engine tries to fetch as many characters as it can by `pattern:.+`, and then shortens that one by one.
|
||||
|
||||
For our task we want another thing. That's what the lazy quantifier mode is for.
|
||||
|
||||
## Lazy mode
|
||||
|
||||
The lazy mode of quantifier is an opposite to the gredy mode. It means: "repeat minimal number of times".
|
||||
|
||||
We can enable it by putting a question mark `pattern:'?'` after the quantifier, so that it becomes `pattern:*?` or `pattern:+?` or even `pattern:??` for `pattern:'?'`.
|
||||
|
||||
To make things clear: usually a question mark `pattern:?` is a quantifier by itself (zero or one), but if added *after another quantifier (or even itself)* it gets another meaning -- it switches the matching mode from greedy to lazy.
|
||||
|
||||
The regexp `pattern:/".+?"/g` works as intended: it finds `match:"witch"` and `match:"broom"`:
|
||||
|
||||
```js run
|
||||
let reg = /".+?"/g;
|
||||
|
||||
let str = 'a "witch" and her "broom" is one';
|
||||
|
||||
alert( str.match(reg) ); // witch, broom
|
||||
```
|
||||
|
||||
To clearly understand the change, let's trace the search step by step.
|
||||
|
||||
1. The first step is the same: it finds the pattern start `pattern:'"'` at the 3rd position:
|
||||
|
||||

|
||||
|
||||
2. The next step is also similar: the engine finds a match for the dot `pattern:'.'`:
|
||||
|
||||

|
||||
|
||||
3. And now the search goes differently. Because we have a lazy mode for `pattern:+?`, the engine doesn't try to match a dot one more time, but stops and tries to match the rest of the pattern `pattern:'"'` right now:
|
||||
|
||||

|
||||
|
||||
If there were a quote there, then the search would end, but there's `'i'`, so there's no match.
|
||||
4. Then the regular expression engine increases the number of repetitions for the dot and tries one more time:
|
||||
|
||||

|
||||
|
||||
Failure again. Then the number of repetitions is increased again and again...
|
||||
5. ...Till the match for the rest of the pattern is found:
|
||||
|
||||

|
||||
|
||||
6. The next search starts from the end of the current match and yield one more result:
|
||||
|
||||

|
||||
|
||||
In this example we saw how the lazy mode works for `pattern:+?`. Quantifiers `pattern:+?` and `pattern:??` work the similar way -- the regexp engine increases the number of repetitions only if the rest of the pattern can't match on the given position.
|
||||
|
||||
**Lazyness is only enabled for the quantifier with `?`.**
|
||||
|
||||
Other quantifiers remain greedy.
|
||||
|
||||
For instance:
|
||||
|
||||
```js run
|
||||
alert( "123 456".match(/\d+ \d+?/g) ); // 123 4
|
||||
```
|
||||
|
||||
1. The pattern `pattern:\d+` tries to match as many numbers as it can (greedy mode), so it finds `match:123` and stops, because the next character is a space `pattern:' '`.
|
||||
2. Then there's a space in pattern, it matches.
|
||||
3. Then there's `pattern:\d+?`. The quantifier is in lazy mode, so it finds one digit `match:4` and tries to check if the rest of the pattern matches from there.
|
||||
|
||||
...But there's nothing in the pattern after `pattern:\d+?`.
|
||||
|
||||
The lazy mode doesn't repeat anything without a need. The pattern finished, so we're done. We have a match `match:123 4`.
|
||||
4. The next search starts from the character `5`.
|
||||
|
||||
```smart header="Optimizations"
|
||||
Modern regular expression engines can optimize internal algorithms to work faster. So they may work a bit different from the described algorithm.
|
||||
|
||||
But to understand how regular expressions work and to build regular expressions, we don't need to know about that. They are only used internally to optimize things.
|
||||
|
||||
Complex regular expressions are hard to optimize, so the search may work exactly as described as well.
|
||||
```
|
||||
|
||||
## Alternative approach
|
||||
|
||||
With regexps, there's often more then one way to do the same thing.
|
||||
|
||||
In our case we can find quoted strings without lazy mode using the regexp `pattern:"[^"]+"`:
|
||||
|
||||
```js run
|
||||
let reg = /"[^"]+"/g;
|
||||
|
||||
let str = 'a "witch" and her "broom" is one';
|
||||
|
||||
alert( str.match(reg) ); // witch, broom
|
||||
```
|
||||
|
||||
The regexp `pattern:"[^"]+"` gives correct results, because it looks for a quote `pattern:'"'` followed by one or more non-quotes `pattern:[^"]`, and then the closing quote.
|
||||
|
||||
When the regexp engine looks for `pattern:[^"]+` it stops the repetitions when it meets the closing quote, and we're done.
|
||||
|
||||
Please note, that this logic does not replace lazy quantifiers!
|
||||
|
||||
It is just different. There are times when we need one or another.
|
||||
|
||||
Let's see one more example where lazy quantifiers fail and this variant works right.
|
||||
|
||||
For instance, we want to find links of the form `<a href="..." class="doc">`, with any `href`.
|
||||
|
||||
Which regular expression to use?
|
||||
|
||||
The first idea might be: `pattern:/<a href=".*" class="doc">/g`.
|
||||
|
||||
Let's check it:
|
||||
```js run
|
||||
let str = '...<a href="link" class="doc">...';
|
||||
let reg = /<a href=".*" class="doc">/g;
|
||||
|
||||
// Works!
|
||||
alert( str.match(reg) ); // <a href="link" class="doc">
|
||||
```
|
||||
|
||||
...But what if there are many links in the text?
|
||||
|
||||
```js run
|
||||
let str = '...<a href="link1" class="doc">... <a href="link2" class="doc">...';
|
||||
let reg = /<a href=".*" class="doc">/g;
|
||||
|
||||
// Wops! Two links in one match!
|
||||
alert( str.match(reg) ); // <a href="link1" class="doc">... <a href="link2" class="doc">
|
||||
```
|
||||
|
||||
Now the result is wrong for the same reason as our "witches" example. The quantifier `pattern:.*` took too many characters.
|
||||
|
||||
The match looks like this:
|
||||
|
||||
```html
|
||||
<a href="....................................." class="doc">
|
||||
<a href="link1" class="doc">... <a href="link2" class="doc">
|
||||
```
|
||||
|
||||
Let's modify the pattern by making the quantifier `pattern:.*?` lazy:
|
||||
|
||||
```js run
|
||||
let str = '...<a href="link1" class="doc">... <a href="link2" class="doc">...';
|
||||
let reg = /<a href=".*?" class="doc">/g;
|
||||
|
||||
// Сработало!
|
||||
alert( str.match(reg) ); // <a href="link1" class="doc">, <a href="link2" class="doc">
|
||||
```
|
||||
|
||||
Now it works, there are two maches:
|
||||
|
||||
```html
|
||||
<a href="....." class="doc"> <a href="....." class="doc">
|
||||
<a href="link1" class="doc">... <a href="link2" class="doc">
|
||||
```
|
||||
|
||||
Why it works -- should be obvious after all explanations above. So let's not stop on the details, but try one more text:
|
||||
|
||||
```js run
|
||||
let str = '...<a href="link1" class="wrong">... <p style="" class="doc">...';
|
||||
let reg = /<a href=".*?" class="doc">/g;
|
||||
|
||||
// Wrong match!
|
||||
alert( str.match(reg) ); // <a href="link1" class="wrong">... <p style="" class="doc">
|
||||
```
|
||||
|
||||
We can see that the regexp matched not just a link, but also a lot of text after it, including `<p...>`.
|
||||
|
||||
Why it happens?
|
||||
|
||||
1. First the regexp finds a link start `match:<a href="`.
|
||||
|
||||
2. Then it looks for `pattern:.*?`, we take one character, then check if there's a match for the rest of the pattern, then take another one...
|
||||
|
||||
The quantifier `pattern:.*?` consumes characters until it meets `match:class="doc">`.
|
||||
|
||||
...And where can it find it? If we look at the text, then we can see that the only `match:class="doc">` is beyound the link, in the tag `<p>`.
|
||||
|
||||
3. So we have match:
|
||||
|
||||
```html
|
||||
<a href="..................................." class="doc">
|
||||
<a href="link1" class="wrong">... <p style="" class="doc">
|
||||
```
|
||||
|
||||
So the lazyness did not work for us here.
|
||||
|
||||
We need the pattern to look for `<a href="...something..." class="doc">`, but both greedy and lazy variants have problems.
|
||||
|
||||
The correct variant would be: `pattern:href="[^"]*"`. It will take all characters inside the `href` attribute till the nearest quote, just what we need.
|
||||
|
||||
A working example:
|
||||
|
||||
```js run
|
||||
let str1 = '...<a href="link1" class="wrong">... <p style="" class="doc">...';
|
||||
let str2 = '...<a href="link1" class="doc">... <a href="link2" class="doc">...';
|
||||
let reg = /<a href="[^"]*" class="doc">/g;
|
||||
|
||||
// Works!
|
||||
alert( str1.match(reg) ); // null, no matches, that's correct
|
||||
alert( str2.match(reg) ); // <a href="link1" class="doc">, <a href="link2" class="doc">
|
||||
```
|
||||
|
||||
## Summary
|
||||
|
||||
Quantifiers have two modes of work:
|
||||
|
||||
Greedy
|
||||
: By default the regular expression engine tries to repeat the quantifier as many times as possible. For instance, `pattern:\d+` consumes all possible digits. When it becomes impossible to consume more (no more digits or string end), then it continues to match the rest of the pattern. If there's no match then it decreases the number of repetitions (backtracks) and tries again.
|
||||
|
||||
Lazy
|
||||
: Enabled by the question mark `pattern:?` after the quantifier. The regexp engine tries to match the rest of the pattern before each repetition of the quantifier.
|
||||
|
||||
As we've seen, the lazy mode is not a "panacea" from the greedy search. An alternative is a "fine-tuned" greedy search, with exclusions. Soon we'll see more examples of it.
|
Before Width: | Height: | Size: 8.2 KiB After Width: | Height: | Size: 8.2 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 7.9 KiB After Width: | Height: | Size: 7.9 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 9.7 KiB After Width: | Height: | Size: 9.7 KiB |
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 8 KiB After Width: | Height: | Size: 8 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 8.1 KiB After Width: | Height: | Size: 8.1 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 8.4 KiB After Width: | Height: | Size: 8.4 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 9.2 KiB After Width: | Height: | Size: 9.2 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
@ -0,0 +1,16 @@
|
|||
|
||||
An integer number is `pattern:\d+`.
|
||||
|
||||
A decimal part is: `pattern:\.\d+`.
|
||||
|
||||
Because the decimal part is optional, let's put it in parentheses with quantifier `pattern:'?'`.
|
||||
|
||||
Finally we have the regexp: `pattern:\d+(\.\d+)?`:
|
||||
|
||||
```js run
|
||||
let reg = /\d+(\.\d+)?/g;
|
||||
|
||||
let str = "1.5 0 12. 123.4.";
|
||||
|
||||
alert( str.match(re) ); // 1.5, 0, 12, 123.4
|
||||
```
|
|
@ -0,0 +1,12 @@
|
|||
# Find positive numbers
|
||||
|
||||
Create a regexp that looks for positive numbers, including those without a decimal point.
|
||||
|
||||
An example of use:
|
||||
```js
|
||||
let reg = /your regexp/g;
|
||||
|
||||
let str = "1.5 0 12. 123.4.";
|
||||
|
||||
alert( str.match(reg) ); // 1.5, 0, 12, 123.4
|
||||
```
|
|
@ -0,0 +1,11 @@
|
|||
A positive number with an optional decimal part is (per previous task): `pattern:\d+(\.\d+)?`.
|
||||
|
||||
Let's add an optional `-` in the beginning:
|
||||
|
||||
```js run
|
||||
let reg = /-?\d+(\.\d+)?/g;
|
||||
|
||||
let str = "-1.5 0 2 -123.4.";
|
||||
|
||||
alert( str.match(reg) ); // -1.5, 0, 2, -123.4
|
||||
```
|
|
@ -0,0 +1,13 @@
|
|||
# Find all numbers
|
||||
|
||||
Write a regexp that looks for all decimal numbers including integer ones, with the floating point and negative ones.
|
||||
|
||||
An example of use:
|
||||
|
||||
```js
|
||||
let reg = /your regexp/g;
|
||||
|
||||
let str = "-1.5 0 2 -123.4.";
|
||||
|
||||
alert( str.match(re) ); // -1.5, 0, 2, -123.4
|
||||
```
|
|
@ -1,15 +1,15 @@
|
|||
# Скобочные группы
|
||||
# Bracket groups
|
||||
|
||||
Часть шаблона может быть заключена в скобки `pattern:(...)`. Такие выделенные части шаблона называют "скобочными выражениями" или "скобочными группами".
|
||||
A part of the pattern can be enclosed in parentheses `pattern:(...)`. That's called a "bracket expression" or a "bracket group".
|
||||
|
||||
У такого выделения есть два эффекта:
|
||||
That has two effects:
|
||||
|
||||
1. Он позволяет выделить часть совпадения в отдельный элемент массива при поиске через [String#match](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/String/match) или [RegExp#exec](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/RegExp/exec).
|
||||
2. Если поставить квантификатор после скобки, то он применится *ко всей скобке*, а не всего лишь к одному символу.
|
||||
1. It allows to place a part of the match into a separate array item when using [String#match](mdn:js/String/match) or [RegExp#exec](mdn:/RegExp/exec) methods.
|
||||
2. If we put a quantifier after the parentheses, it applies to the parentheses as a whole, not the last character.
|
||||
|
||||
[cut]
|
||||
|
||||
## Пример
|
||||
## Example
|
||||
|
||||
В примере ниже, шаблон `pattern:(go)+` находит один или более повторяющихся `pattern:'go'`:
|
||||
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
|
@ -62,3 +62,24 @@ alert( num.match(/^\d+\.\d+$/ig) ); // 12.34, дробь!
|
|||
|
||||
Теперь поиск ищет начало текста, за которым идёт число, затем точка, ещё число и конец текста. Это как раз то, что нужно.
|
||||
|
||||
## TODO
|
||||
|
||||
Add y flag here
|
||||
|
||||
|
||||
## The "y" flag
|
||||
|
||||
The `y` flag means that the match should start exactly at the position specified by the property `regexp.lastIndex`.
|
||||
|
||||
In other words, normally the search is made in the whole string: `pattern:/love/` checks if the word "love" is found on the first position in the string, then in the second etc. In the example above we have a match on the 2nd position.
|
||||
|
||||
But with the `y` flag we need to specify the position in `regexp.lastIndex` (`0` by default), and the match must be exactly on that position.
|
||||
|
||||
Here, the example:
|
||||
|
||||
```js run
|
||||
let str = "I love Javascript!";
|
||||
|
||||
alert( str.search(/love/y) ); // -1 (not found)
|
||||
alert( str.search(/LOVE/i) ); // 2
|
||||
```
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |