Skip to content
46 changes: 23 additions & 23 deletions 9-regular-expressions/10-regexp-greedy-and-lazy/article.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,21 @@

Для початку, знайдемо рядки в лапках, аби потім їх замінити.

Регулярний вираз по типу `pattern:/".+"/g` (лапки з чимось всередині) виглядає підходящим, але це не так!
Регулярний вираз по типу `pattern:/".+"/g` (лапки з чимось всередині) виглядає придатним, але це не так!

Спробуємо його на практиці:

```js run
let regexp = /".+"/g;

let str = 'a "witch" and her "broom" is one';
let str = 'я "мавка" ліс моє "єство" та дух';

alert( str.match(regexp) ); // "witch" and her "broom"
alert( str.match(regexp) ); // "мавка" ліс моє "єство"
```

...Як бачимо, вираз працює не так, як очікувалось!

Замість двох збігів `match:"witch"` та `match:"broom"`, він знайшов один: `match:"witch" and her "broom"`.
Замість двох збігів `match:"мавка"` та `match:"єство"`, він знайшов один: `match:"мавка" ліс моє "єство"`.

Про це можна сказати "жадібність -- причина всіх бід".

Expand All @@ -42,21 +42,21 @@ alert( str.match(regexp) ); // "witch" and her "broom"

1. Першим символом шаблону є одна з лапок `pattern:"`.

Рушій регулярних виразів намагається знайти його на нульовій позиції вихідного рядку `subject:a "witch" and her "broom" is one`, але бачить `subject:a`, тож вважає, що збігу немає.
Рушій регулярних виразів намагається знайти його на нульовій позиції вихідного рядка `subject:я "мавка" ліс моє "єство" та дух`, але бачить `subject:я`, тож вважає, що збігу немає.

Йдемо далі: бере наступну позицію рядку та намагається на ній знайти перший символ шаблону, знову невдача, але, нарешті, необхідний символ знаходиться на третій позиції:
Йдемо далі: бере наступну позицію рядка та намагається на ній знайти перший символ шаблону, знову невдача, але, нарешті, необхідний символ знаходиться на третій позиції:

![](witch_greedy1.svg)

2. Першу з лапок виявлено, після цього рушій намагається знайти збіг для решти шаблону. Він намагається зрозуміти, чи відповідає решта рядка `pattern:.+"`.

В нашому випадку, наступний символ шаблону -- це `pattern:.` (крапка). Він вказує на "будь-який символ, за винятком символу нового рядку", тож наступна літера рядку `match:'w'` підходить під опис:
В нашому випадку, наступний символ шаблону -- це `pattern:.` (крапка). Він вказує на "будь-який символ, за винятком символу нового рядка", тож наступна літера рядка `match:'м'` підходить під опис:

![](witch_greedy2.svg)

3. Після цього, дія крапки повторюється через наявність квантифікатору `pattern:.+`. Рушій регулярних виразів додає до збігу символи один за одним.
3. Після цього, дія крапки повторюється через наявність квантифікатора `pattern:.+`. Рушій регулярних виразів додає до збігу символи один за одним.

...До якого моменту? Крапка приймає усі символи, таким чином зупиняючись тільки досягнувши кінця рядку:
...До якого моменту? Крапка приймає усі символи, таким чином зупиняючись тільки досягнувши кінця рядка:

![](witch_greedy3.svg)

Expand All @@ -68,31 +68,31 @@ alert( str.match(regexp) ); // "witch" and her "broom"

![](witch_greedy4.svg)

Після цього, рушій припускає, що `pattern:.+` завершується одним символом раніше кінця рядку та намагається знайти збіг для решти шаблону, починаючи з тієї позиції.
Після цього, рушій припускає, що `pattern:.+` завершується одним символом раніше кінця рядка та намагається знайти збіг для решти шаблону, починаючи з тієї позиції.

Якби друга з лапок була на цьому місці, то пошук завершився б, але останній символ `subject:'e'` не відповідає цілі пошуку.
Якби друга з лапок була на цьому місці, то пошук завершився б, але останній символ `subject:'х'` не відповідає цілі пошуку.

5. ...Тому рушій зменшує кількість повторів `pattern:.+` на ще один символ:

![](witch_greedy5.svg)

Друга закриваюча лапка `pattern:'"'` не співпадає з `subject:'n'`.
Друга закриваюча лапка `pattern:'"'` не збігається з `subject:'у'`.

6. Рушій продовжує процес повернення: число повторів `pattern:'.'` зменшується доти, доки решта шаблону (в цьому випадку, `pattern:'"'`) не збігається:

![](witch_greedy6.svg)

7. Збіг знайдено.

8. Отож, першим збігом буде:"witch" and her "broom"`. Якщо регулярний вираз має прапорець `pattern:g`, тоді пошук продовжиться з кінця першого збігу. Решта рядку `subject:is one` не містить лапок, тож інших збігів не буде.
8. Отож, першим збігом буде: `"мавка" ліс моє "єство"`. Якщо регулярний вираз має прапорець `pattern:g`, тоді пошук продовжиться з кінця першого збігу. Решта рядка `subject:та дух` не містить лапок, тож інших збігів не буде.

Напевно, це не те, чого ми очікували, але так вже воно працює.

**В жадібному режимі (типово) квантифікований символ повторюється максимально можливу кількість разів.**

Рушій регулярного виразу додає до збігу всі можливі символи для `pattern:.+`, а потім зменшує результат посимвольно, якщо решта шаблону не збігається.

Наша задача потребує іншого підходу. Тут може стати в нагоді лінивий режим.
Наша задача потребує іншого підходу. Тут може стати в пригоді лінивий режим.

## Лінивий режим

Expand All @@ -102,17 +102,17 @@ alert( str.match(regexp) ); // "witch" and her "broom"

Пояснимо кілька моментів: зазвичай, знак питання `pattern:?` сам по собі є квантифікатором (0 чи 1), але змінює значення, якщо його додати *після іншого квантифікатора (або навіть самого себе)* -- він змінює режим пошуку з жадібного на лінивий.

Регулярний вираз `pattern:/".+?"/g` працюватиме, як потрібно: він знайде `match:"witch"` та `match:"broom"`:
Регулярний вираз `pattern:/".+?"/g` працюватиме, як потрібно: він знайде `match:"мавка"` та `match:"єство"`:

```js run
let regexp = /".+?"/g;

let str = 'a "witch" and her "broom" is one';
let str = 'я "мавка" ліс моє "єство" та дух';

alert( str.match(regexp) ); // "witch", "broom"
alert( str.match(regexp) ); // "мавка", "єство"
```

Аби чітко побачити різницю, відслідкуємо процес пошуку покроково.
Аби чітко побачити різницю, простежимо процес пошуку покроково.

1. Перший крок той самий: знаходимо початок шаблону `pattern:'"'` на третій позиції:

Expand All @@ -126,7 +126,7 @@ alert( str.match(regexp) ); // "witch", "broom"

![](witch_lazy3.svg)

Якби на цьому місці була остання з лапок, тоді пошук закінчився б, але бачимо `'i'`, тож збігу немає.
Якби на цьому місці була остання з лапок, тоді пошук закінчився б, але бачимо `'а'`, тож збігу немає.
4. Далі, рушій регулярних виразів збільшує кількість повторів для крапки та ще раз проводить пошук:

![](witch_lazy4.svg)
Expand Down Expand Up @@ -177,9 +177,9 @@ alert( "123 456".match(/\d+ \d+?/) ); // 123 4
```js run
let regexp = /"[^"]+"/g;

let str = 'a "witch" and her "broom" is one';
let str = 'я "мавка" ліс моє "єство" та дух';

alert( str.match(regexp) ); // "witch", "broom"
alert( str.match(regexp) ); // "мавка", "єство"
```

Регулярний вираз `pattern:"[^"]+"` дає правильні результати, бо шукає першу з лапок `pattern:'"'`, за якою слідують один чи більше символів (не лапок) `pattern:[^"]` та друга з лапок в кінці.
Expand Down Expand Up @@ -217,7 +217,7 @@ let regexp = /<a href=".*" class="doc">/g;
alert( str.match(regexp) ); // <a href="link1" class="doc">... <a href="link2" class="doc">
```

Тепер результат неправильний з тієї ж причини, що й у прикладі про "witches". Квантифікатор `pattern:.*` бере забагато символів.
Тепер результат неправильний з тієї ж причини, що й у прикладі про "мавку". Квантифікатор `pattern:.*` бере забагато символів.

Збіг виглядає наступним чином:

Expand Down Expand Up @@ -293,7 +293,7 @@ alert( str2.match(regexp) ); // <a href="link1" class="doc">, <a href="link2" cl
Квантифікатори мають два режими роботи:

Жадібний
: Типово рушій регулярних виразів намагається повторити квантифікований символ максимально можливу кількість разів. Для прикладу, `pattern:\d+` обирає всі можливі цифри. Коли продовжити цей процес неможливо (більше немає цифр/кінець рядку), тоді продовжується пошук збігу для решти шаблону. Якщо збігу немає, він зменшує кількість повторень (повертається) та пробує наново.
: Типово рушій регулярних виразів намагається повторити квантифікований символ максимально можливу кількість разів. Для прикладу, `pattern:\d+` обирає всі можливі цифри. Коли продовжити цей процес неможливо (більше немає цифр/кінець рядка), тоді продовжується пошук збігу для решти шаблону. Якщо збігу немає, він зменшує кількість повторень (повертається) та пробує наново.

Лінивий
: Включається знаком питання `pattern:?` після квантифікатору. Рушій намагається знайти збіг решти шаблону перед кожним повторенням квантифікованого символу.
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading