From 759176b4e588b603e8a5386d87c2905ab36e4160 Mon Sep 17 00:00:00 2001 From: SamGreenberg Date: Sun, 22 Jan 2023 19:06:25 +0300 Subject: [PATCH 1/5] Methods of RegExp and String --- .../17-regexp-methods/article.md | 272 +++++++++--------- 1 file changed, 136 insertions(+), 136 deletions(-) diff --git a/9-regular-expressions/17-regexp-methods/article.md b/9-regular-expressions/17-regexp-methods/article.md index 0cafbbebb..de0662a52 100644 --- a/9-regular-expressions/17-regexp-methods/article.md +++ b/9-regular-expressions/17-regexp-methods/article.md @@ -1,32 +1,32 @@ -# Methods of RegExp and String +# Методи регулярних виразів та рядків -In this article we'll cover various methods that work with regexps in-depth. +В цій статті ми детально розглянемо різні методи для роботи з регулярними виразами. ## str.match(regexp) -The method `str.match(regexp)` finds matches for `regexp` in the string `str`. +Метод `str.match(regexp)` знаходить збіги для `regexp` в рядку `str`. -It has 3 modes: +Він має 3 режими: -1. If the `regexp` doesn't have flag `pattern:g`, then it returns the first match as an array with capturing groups and properties `index` (position of the match), `input` (input string, equals `str`): +1. Якщо `regexp` не має прапору `pattern:g`, тоді він повертає перший збіг у вигляді масиву з групами захоплення та властивостями `index` (позиція збігу), `input` (введений рядок, дорівнює `str`): ```js run - let str = "I love JavaScript"; + let str = "Я люблю JavaScript"; let result = str.match(/Java(Script)/); - alert( result[0] ); // JavaScript (full match) - alert( result[1] ); // Script (first capturing group) + alert( result[0] ); // JavaScript (повний збіг) + alert( result[1] ); // Script (перша група захоплення) alert( result.length ); // 2 - // Additional information: - alert( result.index ); // 7 (match position) - alert( result.input ); // I love JavaScript (source string) + // Додаткова інформація: + alert( result.index ); // 7 (позиція збігу) + alert( result.input ); // Я люблю JavaScript (вихідний рядок) ``` -2. If the `regexp` has flag `pattern:g`, then it returns an array of all matches as strings, without capturing groups and other details. +2. Якщо `regexp` має прапор `pattern:g`, тоді він повертає масив всіх збігів у вигляді рядків, без груп захоплення та інших деталей. ```js run - let str = "I love JavaScript"; + let str = "Я люблю JavaScript"; let result = str.match(/Java(Script)/g); @@ -34,12 +34,12 @@ It has 3 modes: alert( result.length ); // 1 ``` -3. If there are no matches, no matter if there's flag `pattern:g` or not, `null` is returned. +3. Якщо збігів нема, повертається `null`, незалежно від наявності прапору `pattern:g`. - That's an important nuance. If there are no matches, we don't get an empty array, but `null`. It's easy to make a mistake forgetting about it, e.g.: + Це важливий нюанс. Якщо збігів нема, ми отримаємо не порожній масив, а `null`. Легко помилитись, забувши про це: ```js run - let str = "I love JavaScript"; + let str = "Я люблю JavaScript"; let result = str.match(/HTML/); @@ -47,7 +47,7 @@ It has 3 modes: alert(result.length); // Error: Cannot read property 'length' of null ``` - If we want the result to be an array, we can write like this: + Якщо ми хочемо, аби результат був масивом, ми можемо написати: ```js let result = str.match(regexp) || []; @@ -57,48 +57,48 @@ It has 3 modes: [recent browser="new"] -The method `str.matchAll(regexp)` is a "newer, improved" variant of `str.match`. +Метод `str.matchAll(regexp)` - це "новіший, покращений" варіант `str.match`. -It's used mainly to search for all matches with all groups. +В основному, його використовують для пошуку всіх збігів з усіма групами. -There are 3 differences from `match`: +Існує 3 відмінності від `match`: -1. It returns an iterable object with matches instead of an array. We can make a regular array from it using `Array.from`. -2. Every match is returned as an array with capturing groups (the same format as `str.match` without flag `pattern:g`). -3. If there are no results, it returns an empty iterable object instead of `null`. +1. Він повертає ітерований об’єкт із збігами замість масиву. Ми можемо отримати з нього звичайний масив за допомогою `Array.from`. +2. Кожен збіг повертається у вигляді масиву з групами захоплення (той самий формат, що й `str.match` без прапору `pattern:g`). +3. Якщо результатів нема, метод повертає порожній ітерований об’єкт замість `null`. -Usage example: +Приклад використання: ```js run -let str = '

Hello, world!

'; +let str = '

Вітаю, світе!

'; let regexp = /<(.*?)>/g; let matchAll = str.matchAll(regexp); -alert(matchAll); // [object RegExp String Iterator], not array, but an iterable +alert(matchAll); // [object RegExp String Iterator], не масив, але ітерований -matchAll = Array.from(matchAll); // array now +matchAll = Array.from(matchAll); // тепер масив let firstMatch = matchAll[0]; alert( firstMatch[0] ); //

alert( firstMatch[1] ); // h1 alert( firstMatch.index ); // 0 -alert( firstMatch.input ); //

Hello, world!

+alert( firstMatch.input ); //

Вітаю, світе!

``` -If we use `for..of` to loop over `matchAll` matches, then we don't need `Array.from` any more. +Якщо ми використаємо `for..of` для циклічного проходження збігами `matchAll`, тоді ми більше не потребуємо `Array.from`. ## str.split(regexp|substr, limit) -Splits the string using the regexp (or a substring) as a delimiter. +Ділить рядок, використовуючи регулярний вираз (або підрядок) в якості роздільника. -We can use `split` with strings, like this: +Можна використовувати `split` з рядками, як-то: ```js run alert('12-34-56'.split('-')) // array of ['12', '34', '56'] ``` -But we can split by a regular expression, the same way: +Але ми так само можемо ділити за допомогою регулярного виразу: ```js run alert('12, 34, 56'.split(/,\s*/)) // array of ['12', '34', '56'] @@ -106,116 +106,116 @@ alert('12, 34, 56'.split(/,\s*/)) // array of ['12', '34', '56'] ## str.search(regexp) -The method `str.search(regexp)` returns the position of the first match or `-1` if none found: +Метод `str.search(regexp)` повертає позицію першого збігу або `-1`, якщо нічого не знайдено: ```js run -let str = "A drop of ink may make a million think"; +let str = "Чорнил краплина – мільйонів думок причина"; -alert( str.search( /ink/i ) ); // 10 (first match position) +alert( str.search( /ink/i ) ); // 8 (позиція першого збігу) ``` -**The important limitation: `search` only finds the first match.** +**Важливе обмеження: `search` знаходить лише перший збіг.** -If we need positions of further matches, we should use other means, such as finding them all with `str.matchAll(regexp)`. +Якщо нам потрібні позиції подальших збігів, нам слід пошукати інші варіанти, як-то повний пошук за допомогою `str.matchAll(regexp)`. ## str.replace(str|regexp, str|func) -This is a generic method for searching and replacing, one of most useful ones. The swiss army knife for searching and replacing. +Це загальний метод для пошуку та заміни, один з найбільш корисних. «Швейцарський ніж» подібних операцій. -We can use it without regexps, to search and replace a substring: +Ми можемо користуватись ним без регулярних виразів, шукаючи та замінюючи підрядок: ```js run -// replace a dash by a colon +// заміна дефіс на двокрапку alert('12-34-56'.replace("-", ":")) // 12:34-56 ``` -There's a pitfall though. +Але тут є своє підводне каміння. -**When the first argument of `replace` is a string, it only replaces the first match.** +**Коли перший аргумент `replace` є рядком, він замінює лише перший збіг.** -You can see that in the example above: only the first `"-"` is replaced by `":"`. +Ви можете побачити це в попередньому прикладі: лише перше `"-"` замінюється на `":"`. -To find all hyphens, we need to use not the string `"-"`, but a regexp `pattern:/-/g`, with the obligatory `pattern:g` flag: +Аби знайти всі дефіси, нам потрібно використати не рядок `"-"`, а регулярний вираз `pattern:/-/g`, з обов’язковим прапором `pattern:g`: ```js run -// replace all dashes by a colon +// заміна всіх дефісів на двокрапку alert( '12-34-56'.replace( *!*/-/g*/!*, ":" ) ) // 12:34:56 ``` -The second argument is a replacement string. We can use special characters in it: +Другий аргумент є рядком для заміни. В ньому можуть міститись спеціальні символи: -| Symbols | Action in the replacement string | +| Символи | Роль в рядку для заміни | |--------|--------| -|`$&`|inserts the whole match| -|$`|inserts a part of the string before the match| -|`$'`|inserts a part of the string after the match| -|`$n`|if `n` is a 1-2 digit number, inserts the contents of n-th capturing group, for details see [](info:regexp-groups)| -|`$`|inserts the contents of the parentheses with the given `name`, for details see [](info:regexp-groups)| -|`$$`|inserts character `$` | +|`$&`|вставляє повний збіг| +|$`|вставляє частину рядку, що йшла перед збігом| +|`$'`|вставляє частину рядку, що йшла після збігу| +|`$n`|якщо`n` є одно-двоцифровим числом, вставляє вміст n-ної групи захоплення, детальніше тут [](info:regexp-groups)| +|`$`|вставляє вміст іменованих дужок `name`, детальніше тут [](info:regexp-groups)| +|`$$`|вставляє символ `$` | -For instance: +Для прикладу: ```js run -let str = "John Smith"; +let str = "Іван Сірко"; -// swap first and last name -alert(str.replace(/(john) (smith)/i, '$2, $1')) // Smith, John +// переставляє місцями ім’я та прізвище +alert(str.replace(/(іван) (сірко)/i, '$2, $1')) // Сірко, Іван ``` -**For situations that require "smart" replacements, the second argument can be a function.** +**Для ситуацій, які потребують "розумної" перестановки, другий аргумент може бути функцією.** -It will be called for each match, and the returned value will be inserted as a replacement. +Вона викликатиметься для кожного збігу, а повернене значення вставлятиметься як заміна. -The function is called with arguments `func(match, p1, p2, ..., pn, offset, input, groups)`: +Функція містить аргументи `func(match, p1, p2, ..., pn, offset, input, groups)`: -1. `match` -- the match, -2. `p1, p2, ..., pn` -- contents of capturing groups (if there are any), -3. `offset` -- position of the match, -4. `input` -- the source string, -5. `groups` -- an object with named groups. +1. `match` -- збіг, +2. `p1, p2, ..., pn` -- вміст груп захоплення (якщо є в наявності), +3. `offset` -- позиція збігу, +4. `input` -- вихідний рядок, +5. `groups` -- об’єкт з іменованими групами. -If there are no parentheses in the regexp, then there are only 3 arguments: `func(str, offset, input)`. +Якщо в регулярному виразі нема дужок, тоді є лише 3 аргументи: `func(str, offset, input)`. -For example, let's uppercase all matches: +Наприклад, приведемо всі збіги до верхнього регістру: ```js run -let str = "html and css"; +let str = "html та css"; let result = str.replace(/html|css/gi, str => str.toUpperCase()); -alert(result); // HTML and CSS +alert(result); // HTML та CSS ``` -Replace each match by its position in the string: +Замінимо кожен збіг на його позицію в рядку: ```js run -alert("Ho-Ho-ho".replace(/ho/gi, (match, offset) => offset)); // 0-3-6 +alert("Хо-хо-хо".replace(/хо/gi, (match, offset) => offset)); // 0-3-6 ``` -In the example below there are two parentheses, so the replacement function is called with 5 arguments: the first is the full match, then 2 parentheses, and after it (not used in the example) the match position and the source string: +В прикладі нижче, наявні дві дужки, тож функція заміни матиме 5 аргументів: перший є повним збігом, далі 2 дужки, після цього (не використані в прикладі) позиція збігу та вихідний рядок: ```js run -let str = "John Smith"; +let str = "Іван Сірко"; let result = str.replace(/(\w+) (\w+)/, (match, name, surname) => `${surname}, ${name}`); -alert(result); // Smith, John +alert(result); // Сірко, Іван ``` -If there are many groups, it's convenient to use rest parameters to access them: +Якщо груп багато, зручно використовувати залишкові параметри для доступу до них: ```js run -let str = "John Smith"; +let str = "Іван Сірко"; let result = str.replace(/(\w+) (\w+)/, (...match) => `${match[2]}, ${match[1]}`); -alert(result); // Smith, John +alert(result); // Сірко, Іван ``` -Or, if we're using named groups, then `groups` object with them is always the last, so we can obtain it like this: +В іншому випадку, якщо ми використовуємо іменовані групи, тоді об’єкт `groups` завжди йде останнім після них, тож ми можемо отримати його наступним чином: ```js run -let str = "John Smith"; +let str = "Іван Сірко"; let result = str.replace(/(?\w+) (?\w+)/, (...match) => { let groups = match.pop(); @@ -223,139 +223,139 @@ let result = str.replace(/(?\w+) (?\w+)/, (...match) => { return `${groups.surname}, ${groups.name}`; }); -alert(result); // Smith, John +alert(result); // Сірко, Іван ``` -Using a function gives us the ultimate replacement power, because it gets all the information about the match, has access to outer variables and can do everything. +Використання функції максимально розкриває можливості заміни, тому що ми отримуємо всю інформацію про збіг, доступ до зовнішніх змінних та можемо робити все, що потрібно. ## str.replaceAll(str|regexp, str|func) -This method is essentially the same as `str.replace`, with two major differences: +Цей метод, по суті, такий самий, що й `str.replace`, з двома значними відмінностями: -1. If the first argument is a string, it replaces *all occurences* of the string, while `replace` replaces only the *first occurence*. -2. If the first argument is a regular expression without the `g` flag, there'll be an error. With `g` flag, it works the same as `replace`. +1. Якщо перший аргумент є рядком, він замінює *всі входження* в рядку, тоді як `replace` замінює лише *перше входження*. +2. Якщо перший аргумент є регулярним виразом без прапору `g`, виникне помилка. З прапором `g`, метод працюватиме аналогічно до `replace`. -The main use case for `replaceAll` is replacing all occurences of a string. +Основний випадок використання `replaceAll` - заміна всіх входжень збігу в рядку. -Like this: +Наприклад: ```js run -// replace all dashes by a colon +// замінює всі дефіси на двокрапку alert('12-34-56'.replaceAll("-", ":")) // 12:34:56 ``` ## regexp.exec(str) -The `regexp.exec(str)` method returns a match for `regexp` in the string `str`. Unlike previous methods, it's called on a regexp, not on a string. +Метод `regexp.exec(str)` повертає збіг для `regexp` в рядку `str`. На відміну від попередніх методів, він застосовується до регулярного виразу, а не рядку. -It behaves differently depending on whether the regexp has flag `pattern:g`. +Його поведінка залежить від наявності в регулярному виразі прапору `pattern:g`. -If there's no `pattern:g`, then `regexp.exec(str)` returns the first match exactly as `str.match(regexp)`. This behavior doesn't bring anything new. +Якщо нема прапору `pattern:g`, тоді `regexp.exec(str)` повертає перший збіг у вигляді `str.match(regexp)`. Ця поведінка не додає нічого нового. -But if there's flag `pattern:g`, then: -- A call to `regexp.exec(str)` returns the first match and saves the position immediately after it in the property `regexp.lastIndex`. -- The next such call starts the search from position `regexp.lastIndex`, returns the next match and saves the position after it in `regexp.lastIndex`. -- ...And so on. -- If there are no matches, `regexp.exec` returns `null` and resets `regexp.lastIndex` to `0`. +Але за наявності `pattern:g`: +- Виклик `regexp.exec(str)` повертає перший збіг та зберігає позицію після нього всередині властивості `regexp.lastIndex`. +- Наступний виклик починає пошук з позиції `regexp.lastIndex`, повертає наступний збіг та зберігає позицію після в `regexp.lastIndex`. +- ...І так далі. +- Якщо збігів нема, `regexp.exec` повертає `null` та скидає `regexp.lastIndex` до `0`. -So, repeated calls return all matches one after another, using property `regexp.lastIndex` to keep track of the current search position. +Тож, повторювані виклики один за одним повертають всі збіги, використовуючи властивість `regexp.lastIndex` для відслідковування поточної позиції пошуку. -In the past, before the method `str.matchAll` was added to JavaScript, calls of `regexp.exec` were used in the loop to get all matches with groups: +В минулому, коли метод `str.matchAll` ще не був доданий в JavaScript, виклики `regexp.exec` використовувались в циклі для отримання всіх збігів, разом з групами: ```js run -let str = 'More about JavaScript at https://javascript.info'; +let str = ''Детальніше про JavaScript тут https://javascript.info'; let regexp = /javascript/ig; let result; while (result = regexp.exec(str)) { - alert( `Found ${result[0]} at position ${result.index}` ); - // Found JavaScript at position 11, then - // Found javascript at position 33 + alert( `${result[0]} знайдений на позиції ${result.index}` ); + // JavaScript знайдений на позиції 11 + // javascript знайдений на позиції 33 } ``` -This works now as well, although for newer browsers `str.matchAll` is usually more convenient. +Нині це теє працює, хоча для новіших браузерів `str.matchAll`, зазвичай, зручніший. -**We can use `regexp.exec` to search from a given position by manually setting `lastIndex`.** +**Можна використовувати `regexp.exec` для пошуку із зазначеної позиції, вручну змінивши `lastIndex`.** -For instance: +Для прикладу: ```js run -let str = 'Hello, world!'; +let str = 'Вітаю, світе!'; -let regexp = /\w+/g; // without flag "g", lastIndex property is ignored -regexp.lastIndex = 5; // search from 5th position (from the comma) +let regexp = /\w+/g; // без прапору "g", властивість lastIndex ігнорується +regexp.lastIndex = 5; // пошук з п’ятої позиції (з коми) -alert( regexp.exec(str) ); // world +alert( regexp.exec(str) ); // світе ``` -If the regexp has flag `pattern:y`, then the search will be performed exactly at the position `regexp.lastIndex`, not any further. +Якщо регулярний вираз має прапор `pattern:y`, тоді пошук проводитиметься абсолютно чітко з позиції `regexp.lastIndex`. -Let's replace flag `pattern:g` with `pattern:y` in the example above. There will be no matches, as there's no word at position `5`: +У прикладі зверху, замінимо прапор `pattern:g` на `pattern:y`. Збігів не буде, бо на 5 позиції нема слова: ```js run -let str = 'Hello, world!'; +let str = 'Вітаю, світе!'; let regexp = /\w+/y; -regexp.lastIndex = 5; // search exactly at position 5 +regexp.lastIndex = 5; // пошук саме з 5 позиції alert( regexp.exec(str) ); // null ``` -That's convenient for situations when we need to "read" something from the string by a regexp at the exact position, not somewhere further. +Це зручно для ситуацій, коли нам потрібно "зчитати" щось з рядку регулярним виразом з чітко визначеної позиції, не шукаючи далі. ## regexp.test(str) -The method `regexp.test(str)` looks for a match and returns `true/false` whether it exists. +Метод `regexp.test(str)` шукає збіг та повертає `true/false` в залежності від його наявності. -For instance: +Для прикладу: ```js run -let str = "I love JavaScript"; +let str = "Я люблю JavaScript"; -// these two tests do the same -alert( *!*/love/i*/!*.test(str) ); // true -alert( str.search(*!*/love/i*/!*) != -1 ); // true +// ці два тести роблять одне й те саме +alert( *!*/люблю/i*/!*.test(str) ); // true +alert( str.search(*!*/люблю/i*/!*) != -1 ); // true ``` -An example with the negative answer: +Приклад з негативним результатом: ```js run -let str = "Bla-bla-bla"; +let str = "Бла-бла-бла"; -alert( *!*/love/i*/!*.test(str) ); // false -alert( str.search(*!*/love/i*/!*) != -1 ); // false +alert( *!*/люблю/i*/!*.test(str) ); // false +alert( str.search(*!*/люблю/i*/!*) != -1 ); // false ``` -If the regexp has flag `pattern:g`, then `regexp.test` looks from `regexp.lastIndex` property and updates this property, just like `regexp.exec`. +Якщо регулярний вираз має прапор `pattern:g`, тоді `regexp.test` проводить пошук, починаючи з `regexp.lastIndex` та оновлює цю властивість, аналогічно до `regexp.exec`. -So we can use it to search from a given position: +Тож ми можемо використовувати його для пошуку з визначеної позиції: ```js run -let regexp = /love/gi; +let regexp = /люблю/gi; -let str = "I love JavaScript"; +let str = "Я люблю JavaScript"; -// start the search from position 10: +// починаємо пошук з позиції 10: regexp.lastIndex = 10; -alert( regexp.test(str) ); // false (no match) +alert( regexp.test(str) ); // false (збігу нема) ``` -````warn header="Same global regexp tested repeatedly on different sources may fail" -If we apply the same global regexp to different inputs, it may lead to wrong result, because `regexp.test` call advances `regexp.lastIndex` property, so the search in another string may start from non-zero position. +````warn header="Один і той самий глобальний регулярний вираз, протестований багато разів на різних даних, може помилятись" +Якщо ми застосуємо один і той самий глобальний регулярний вираз до різних вхідних даних, можемо отримати неправильні результати, бо виклик `regexp.test` посуває властивість `regexp.lastIndex`, тож пошук в іншому рядку може початись з позиції, відмінною від нуля. -For instance, here we call `regexp.test` twice on the same text, and the second time fails: +Для прикладу, ми викликаємо `regexp.test` двічі для одного тексту, та другий раз повертається помилковий результат: ```js run -let regexp = /javascript/g; // (regexp just created: regexp.lastIndex=0) +let regexp = /javascript/g; // (новостворений регулярний вираз: regexp.lastIndex=0) -alert( regexp.test("javascript") ); // true (regexp.lastIndex=10 now) +alert( regexp.test("javascript") ); // true (наразі regexp.lastIndex=10) alert( regexp.test("javascript") ); // false ``` -That's exactly because `regexp.lastIndex` is non-zero in the second test. +Проблема саме в `regexp.lastIndex`, що не є нульовим під час виконання другого тесту. -To work around that, we can set `regexp.lastIndex = 0` before each search. Or instead of calling methods on regexp, use string methods `str.match/search/...`, they don't use `lastIndex`. +Оминути це можна, встановлюючи `regexp.lastIndex = 0` перед кожним пошуком. Іншим рішенням є використання методів рядків `str.match/search/...` замість методів регулярних виразів, так як вони не використовують `lastIndex`. ```` From a536fb45ab538865e29561018e3b7ea2c0430ee7 Mon Sep 17 00:00:00 2001 From: SamGreenberg Date: Thu, 26 Jan 2023 23:44:11 +0300 Subject: [PATCH 2/5] Greedy and lazy quantifiers --- .../1-lazy-greedy/solution.md | 6 +- .../1-lazy-greedy/task.md | 4 +- .../3-find-html-comments/solution.md | 10 +- .../3-find-html-comments/task.md | 10 +- .../4-find-html-tags-greedy-lazy/solution.md | 2 +- .../4-find-html-tags-greedy-lazy/task.md | 10 +- .../10-regexp-greedy-and-lazy/article.md | 214 +++++++++--------- 7 files changed, 128 insertions(+), 128 deletions(-) diff --git a/9-regular-expressions/10-regexp-greedy-and-lazy/1-lazy-greedy/solution.md b/9-regular-expressions/10-regexp-greedy-and-lazy/1-lazy-greedy/solution.md index b8e022223..d59edba55 100644 --- a/9-regular-expressions/10-regexp-greedy-and-lazy/1-lazy-greedy/solution.md +++ b/9-regular-expressions/10-regexp-greedy-and-lazy/1-lazy-greedy/solution.md @@ -1,6 +1,6 @@ -The result is: `match:123 4`. +Результат: `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`. +Для початку, лінивий `pattern:\d+?` намагається взяти мінімальну можливу кількість цифр, але він має дійти до пробілу, тож обирається `match:123`. -Then the second `\d+?` takes only one digit, because that's enough. +Далі, другий `\d+?` обере лише одну тему, бо цього достатньо. diff --git a/9-regular-expressions/10-regexp-greedy-and-lazy/1-lazy-greedy/task.md b/9-regular-expressions/10-regexp-greedy-and-lazy/1-lazy-greedy/task.md index 596f61a4e..fe351794f 100644 --- a/9-regular-expressions/10-regexp-greedy-and-lazy/1-lazy-greedy/task.md +++ b/9-regular-expressions/10-regexp-greedy-and-lazy/1-lazy-greedy/task.md @@ -1,6 +1,6 @@ -# A match for /d+? d+?/ +# Збіг для /d+? d+?/ -What's the match here? +Який результат ми отримаємо? ```js alert( "123 456".match(/\d+? \d+?/g) ); // ? diff --git a/9-regular-expressions/10-regexp-greedy-and-lazy/3-find-html-comments/solution.md b/9-regular-expressions/10-regexp-greedy-and-lazy/3-find-html-comments/solution.md index 0244963d1..184954114 100644 --- a/9-regular-expressions/10-regexp-greedy-and-lazy/3-find-html-comments/solution.md +++ b/9-regular-expressions/10-regexp-greedy-and-lazy/3-find-html-comments/solution.md @@ -1,15 +1,15 @@ -We need to find the beginning of the comment `match:`. +Нам потрібно знайти початок коментарю `match:`. -An acceptable variant is `pattern:` -- the lazy quantifier makes the dot stop right before `match:-->`. We also need to add flag `pattern:s` for the dot to include newlines. +Прийнятним є варіант `pattern:` -- лінивий квантифікатор зупиняє крапку прямо перед `match:-->`. Нам також треба додати прапор `pattern:s`, аби крапка включала символи нового рядку. -Otherwise multiline comments won't be found: +Інакше коментарі на кілька рядків не знаходитимуться: ```js run let regexp = //gs; -let str = `... .. .. `; -alert( str.match(regexp) ); // '', '' +alert( str.match(regexp) ); // '', '' ``` diff --git a/9-regular-expressions/10-regexp-greedy-and-lazy/3-find-html-comments/task.md b/9-regular-expressions/10-regexp-greedy-and-lazy/3-find-html-comments/task.md index 551d9c725..98f7e53d0 100644 --- a/9-regular-expressions/10-regexp-greedy-and-lazy/3-find-html-comments/task.md +++ b/9-regular-expressions/10-regexp-greedy-and-lazy/3-find-html-comments/task.md @@ -1,13 +1,13 @@ -# Find HTML comments +# Знайти HTML коментарі -Find all HTML comments in the text: +Знайти всі HTML коментарі в тексті: ```js -let regexp = /your regexp/g; +let regexp = /ваш регулярний вираз/g; -let str = `... .. .. `; -alert( str.match(regexp) ); // '', '' +alert( str.match(regexp) ); // '', '' ``` diff --git a/9-regular-expressions/10-regexp-greedy-and-lazy/4-find-html-tags-greedy-lazy/solution.md b/9-regular-expressions/10-regexp-greedy-and-lazy/4-find-html-tags-greedy-lazy/solution.md index b4d9f7496..72e9b112d 100644 --- a/9-regular-expressions/10-regexp-greedy-and-lazy/4-find-html-tags-greedy-lazy/solution.md +++ b/9-regular-expressions/10-regexp-greedy-and-lazy/4-find-html-tags-greedy-lazy/solution.md @@ -1,5 +1,5 @@ -The solution is `pattern:<[^<>]+>`. +Відповідь: `pattern:<[^<>]+>`. ```js run let regexp = /<[^<>]+>/g; diff --git a/9-regular-expressions/10-regexp-greedy-and-lazy/4-find-html-tags-greedy-lazy/task.md b/9-regular-expressions/10-regexp-greedy-and-lazy/4-find-html-tags-greedy-lazy/task.md index 6759152ff..172fb7982 100644 --- a/9-regular-expressions/10-regexp-greedy-and-lazy/4-find-html-tags-greedy-lazy/task.md +++ b/9-regular-expressions/10-regexp-greedy-and-lazy/4-find-html-tags-greedy-lazy/task.md @@ -1,15 +1,15 @@ -# Find HTML tags +# Знайти HTML теги -Create a regular expression to find all (opening and closing) HTML tags with their attributes. +Створити регулярний вираз для пошуку всіх (відкриваючих та закриваючих) HTML тегів з їх атрибутами. -An example of use: +Приклад використання: ```js run -let regexp = /your regexp/g; +let regexp = /ваш регулярний вираз/g; let str = '<> '; alert( str.match(regexp) ); // '', '', '' ``` -Here we assume that tag attributes may not contain `<` and `>` (inside quotes too), that simplifies things a bit. +Тут ми припускаємо, що атрибути тегу не містять `<` та `>` (внутрішні лапки) для спрощення задачі. diff --git a/9-regular-expressions/10-regexp-greedy-and-lazy/article.md b/9-regular-expressions/10-regexp-greedy-and-lazy/article.md index 2f656479d..aa73659d0 100644 --- a/9-regular-expressions/10-regexp-greedy-and-lazy/article.md +++ b/9-regular-expressions/10-regexp-greedy-and-lazy/article.md @@ -1,20 +1,20 @@ -# 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+/`. +Нам варто добре розуміти як працює пошук, якщо ми плануємо розглядати щось складніше за `pattern:/\d+/`. -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»`. There exist other quotes, such as `„Witam, świat!”` (Polish) or `「你好,世界」` (Chinese), but for our task let's choose `«...»`. +Наприклад: `"Hello, world"` має перетворитись на `«Hello, world»`. Існують й інші види лапок, як-то `„Witam, świat!”` (польські) or `「你好,世界」` (китайські), але для нашої задачі виберемо `«...»`. -The first thing to do is to locate quoted strings, and then we can replace them. +Для початку, знайдемо рядки в лапках, аби потім їх замінити. -A regular expression like `pattern:/".+"/g` (a quote, then something, then the other quote) may seem like a good fit, but it isn't! +Регулярний вираз по типу `pattern:/".+"/g` (лапки з чимось всередині) виглядає підходящим, але це не так! -Let's try it: +Спробуємо його на практиці: ```js run let regexp = /".+"/g; @@ -24,85 +24,85 @@ let str = 'a "witch" and her "broom" is one'; alert( str.match(regexp) ); // "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"`. +Замість двох збігів `match:" witch "` та `match:" broom"`, він знайшов один: `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 - - Try to match the pattern at that position. - - 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:".+"`. +Ці загальні фрази не пояснюють, чому регулярний вираз працює неправильно, тож вточнимо, як пошук працює для патерну `pattern:".+"`. -1. The first pattern character is a quote `pattern:"`. +1. Першим символом патерну є одна з лапок `pattern:"`. - The regular expression engine tries to find it at the zero position of the source string `subject:a "witch" and her "broom" is one`, but there's `subject:a` there, so there's immediately no match. + Рушій регулярного виразу намагається знайти його на нульовій позиції вихідного рядку `subject:a "witch" and her "broom" is one`, але бачить `subject:a`, тож одразу зрозуміло, що збігу нема. - Then it advances: goes to the next positions in the source string and tries to find the first character of the pattern there, fails again, and finally finds the quote at the 3rd position: + Йдемо далі: бере наступну позицію рядку та намагається на ній знайти перший символ патерну, знову невдача, але, нарешті, необхідний символ знаходиться на третій позиції: ![](witch_greedy1.svg) -2. The quote is detected, and then the engine tries to find a match for the rest of the pattern. It tries to see if the rest of the subject string conforms to `pattern:.+"`. +2. Першу з лапок виявлено, після цього рушій намагається знайти збіг для решти патерну. Він намагається зрозуміти, чи відповідає решта рядку `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: + IВ нашому випадку, наступний символ патерну - це `pattern:.` (крапка). Він вказує на "будь-який символ, за винятком символу нового рядку", тож наступна літера рядку `match:'w'` підходить під опис: ![](witch_greedy2.svg) -3. Then the dot repeats because of the quantifier `pattern:.+`. The regular expression engine adds to the match one character after another. +3. Після цього, дія крапки повторюється через наявність квантифікатору `pattern:.+`. Рушій регулярного виразу додає до збігу символи один за одним. - ...Until when? All characters match the dot, so it only stops when it reaches the end of the string: + ...До якого моменту? Крапка приймає усі символи, таким чином зупиняючись тільки досягнувши кінця рядку: ![](witch_greedy3.svg) -4. Now the engine finished repeating `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! +4. Тепер рушій завершив повтори `pattern:.+` та намагається знайти наступний символ патерну – другу з лапок `pattern:"`. Але виникає проблема: рядок закінчився, символів більше нема! - The regular expression engine understands that it took too many `pattern:.+` and starts to *backtrack*. + Рушій регулярного виразу розуміє, що додав забагато `pattern:.+` та починає *повернення*. - In other words, it shortens the match for the quantifier by one character: + Іншими словами, він скорочує збіг для квантифікатора на один символ: ![](witch_greedy4.svg) - Now it assumes that `pattern:.+` ends one character before the string end and tries to match the rest of the pattern from that position. + Після цього, рушій припускає, що `pattern:.+` завершується одним символом раніше кінця рядку та намагається знайти збіг для решти патерну, починаючи з тієї позиції. - If there were a quote there, then the search would end, but the last character is `subject:'e'`, so there's no match. + Якби друга з лапок була на цьому місці, то пошук завершився б, але останній символ `subject:'e'` не відповідає цілі пошуку. -5. ...So the engine decreases the number of repetitions of `pattern:.+` by one more character: +5. ...Тому рушій зменшує кількість повторів `pattern:.+` на ще один символ: ![](witch_greedy5.svg) - The quote `pattern:'"'` does not match `subject:'n'`. + Друга з лапок `pattern:'"'` не співпадає з `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: +6. Рушій продовжує процес повернення: число повторів `pattern:'.'` зменшується доти, доки решта патерну (в цьому випадку, `pattern:'"'`) не збігається: ![](witch_greedy6.svg) -7. The match is complete. +7. Збіг знайдено. -8. So the first match is `match:"witch" and her "broom"`. If the regular expression has flag `pattern:g`, then the search will continue from where the first match ends. There are no more quotes in the rest of the string `subject:is one`, so no more results. +8. Отож, першим збігом буде:"witch" and her "broom"`. Якщо регулярний вираз має прапор `pattern:g`, тоді пошук продовжиться з кінця першого збігу. Решта рядку `subject:is one` не містить лапок, тож інших збігів не буде. -That's probably not what we expected, but that's how it works. +Можливо, це протирічить нашим очікуванням, але так вже воно працює. -**In the greedy mode (by default) a quantified character is repeated as many times as possible.** +**В жадібному режимі (за замовчуванням) квантифікований символ повторюється максимально можливу кількість разів.** -The regexp engine adds to the match as many characters as it can for `pattern:.+`, and then shortens that one by one, if the rest of the pattern doesn't match. +Рушій регулярного виразу додає до збігу всі можливі символи для `pattern:.+`, а потім зменшує результат посимвольно, якщо решта патерну не знаходить збігу. -For our task we want another thing. That's where a lazy mode can help. +Наша таска потребує іншого підходу. Тут може стати в пригоді лінивий режим. -## Lazy mode +## Лінивий режим -The lazy mode of quantifiers is an opposite to the greedy 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:'?'`. +Ми можемо включити його, поставивши знак питання `pattern:'?'` після квантифікатора, і отримати `pattern:*?`, `pattern:+?` чи навіть `pattern:??` для `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. +Пояснимо кілька моментів: зазвичай, знак питання `pattern:?` сам по собі є квантифікатором (0 чи 1), але змінює значення, якщо його додати *після іншого квантифікатора (або навіть самого себе)* -- він змінює режим пошуку з жадібного на лінивий. -The regexp `pattern:/".+?"/g` works as intended: it finds `match:"witch"` and `match:"broom"`: +Регулярний вираз `pattern:/".+?"/g` працюватиме, як потрібно: він знайде `match:"witch"` та `match:"broom"`: ```js run let regexp = /".+?"/g; @@ -112,67 +112,67 @@ let str = 'a "witch" and her "broom" is one'; alert( str.match(regexp) ); // "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: +1. Перший крок той самий: знаходимо початок патерну `pattern:'"'` на третій позиції: ![](witch_greedy1.svg) -2. The next step is also similar: the engine finds a match for the dot `pattern:'.'`: +2. Наступний крок теж подібний: рушій знаходить збіг для крапки `pattern:'.'`: ![](witch_greedy2.svg) -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: +3. З цього моменту пошук йде іншим шляхом. Для `pattern:+?` включений лінивий режим, тож тепер рушій більше не намагається знайти збіг для крапки, зупиняється та намагається знайти збіг для решти патерну `pattern:'"'`: ![](witch_lazy3.svg) - 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: + Якби на цьому місці була остання з лапок, тоді пошук закінчився б, але бачимо `'i'`, тож збігу немає. +4. Далі, рушій регулярного виразу збільшує кількість повторів для крапки та ще раз проводить пошук: ![](witch_lazy4.svg) - Failure again. Then the number of repetitions is increased again and again... -5. ...Till the match for the rest of the pattern is found: + Знову невдача, тож кількість повторів крок за кроком збільшується... +5. ...До моменту знаходження збігу для решти патерну: ![](witch_lazy5.svg) -6. The next search starts from the end of the current match and yield one more result: +6. Наступний пошук починається з кінця поточного збігу та приносить ще один результат: ![](witch_lazy6.svg) -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. +В цьому прикладі, ми побачили, як працює лінивий режим для `pattern:+?`. Квантифікатори `pattern:*?` та `pattern:??` працюють за схожою схемою – рушій регулярного виразу збільшує кількість повторень, тільки якщо решта патерну не знаходить збігу на поточній позиції. -**Laziness is only enabled for the quantifier with `?`.** +**Лінивий режим можна включити лише за допомогою `?`.** -Other quantifiers remain greedy. +Інші квантифікатори залишаються жадібними. -For instance: +Для прикладу: ```js run alert( "123 456".match(/\d+ \d+?/) ); // 123 4 ``` -1. The pattern `pattern:\d+` tries to match as many digits 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 the 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. +1. Патерн `pattern:\d+` намагається додати в збіг якомога більше цифр (жадібний режим), тож він знаходить `match:123` та зупиняється, тому що наступним йде пробіл `pattern:' '`. +2. Далі в патерні працює пробіл, він бачить збіг. +3. Після цього, маємо `pattern:\d+?`. Квантифікатор в лінивому режимі, тож він знаходить одну цифру `match:4` та, починаючи з цієї позиції, переходить до перевірки на збіг для решти патерну. - ...But there's nothing in the pattern after `pattern:\d+?`. + ...Але після `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`. + Лінивий режим не повторює нічого без потреби. Патерн завершився, а з ним і наша робота. Ми знайшли збіг `match:123 4`. ```smart header="Optimizations" -Modern regular expression engines can optimize internal algorithms to work faster. So they may work a bit differently 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 than one way to do the same thing. +Працюючи з регулярними виразами, часто можна знайти декілька способів зробити одну й ту саму річ. -In our case we can find quoted strings without lazy mode using the regexp `pattern:"[^"]+"`: +В нашому випадку, ми можемо знайти рядки в лапках без лінивого режиму, використовуючи `pattern:"[^"]+"`: ```js run let regexp = /"[^"]+"/g; @@ -182,120 +182,120 @@ let str = 'a "witch" and her "broom" is one'; alert( str.match(regexp) ); // "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. +Регулярний вираз `pattern:"[^"]+"` дає правильні результати, бо шукає першу з лапок `pattern:'"'`, за якою слідують один чи більше символів (не лапок) `pattern:[^"]` та друга з лапок в кінці. -When the regexp engine looks for `pattern:[^"]+` it stops the repetitions when it meets the closing quote, and we're done. +Коли рушій регулярного виразу шукає `pattern:[^"]+`, він припиняє повторення, як тільки зустрічає другу з лапок, і все. -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 an example where lazy quantifiers fail and this variant works right.** +**Розглянемо приклад, в якому ліниві квантифікатори помиляються, на відміну від другого варіанту.** -For instance, we want to find links of the form ``, with any `href`. +Скажімо, ми хочемо знайти посилання форми ``, з будь-яким `href`. -Which regular expression to use? +Який регулярний вираз використати? -The first idea might be: `pattern://g`. +Першим на думку приходить: `pattern://g`. -Let's check it: +Спробуємо: ```js run let str = '......'; let regexp = //g; -// Works! +// Працює! alert( str.match(regexp) ); // ``` -It worked. But let's see what happens if there are many links in the text? +Спрацювало. Але подивимось, що станеться, якщо текст містить багато посилань? ```js run let str = '...... ...'; let regexp = //g; -// Whoops! Two links in one match! +// Йой! Два посилання в одному збігу! alert( str.match(regexp) ); // ... ``` -Now the result is wrong for the same reason as our "witches" example. The quantifier `pattern:.*` took too many characters. +Тепер результат неправильний з тієї ж причини, що й у прикладі про "witches". Квантифікатор `pattern:.*` бере забагато символів. -The match looks like this: +Збіг виглядає наступним чином: ```html ... ``` -Let's modify the pattern by making the quantifier `pattern:.*?` lazy: +Змінимо патерн, зробивши квантифікатор `pattern:.*?` лінивим: ```js run let str = '...... ...'; let regexp = //g; -// Works! +// Працює! alert( str.match(regexp) ); // , ``` -Now it seems to work, there are two matches: +Ніби працює, маємо два збіги: ```html ... ``` -...But let's test it on one more text input: +...Але перевіримо на інших даних: ```js run let str = '......

...'; let regexp = //g; -// Wrong match! +// Хибна відповідь! alert( str.match(regexp) ); // ...

``` -Now it fails. The match includes not just a link, but also a lot of text after it, including ``. +Маємо провал. Збіг не обмежується посиланням, а містить також купу тексту, разом з ``. -Why? +Чому? -That's what's going on: +Ось процес виконання: -1. First the regexp finds a link start `match:` (none). -3. Then takes another character into `pattern:.*?`, and so on... until it finally reaches `match:" class="doc">`. +1. Спочатку, регулярний вираз знаходить початок посилання `match:` (нема). +3. Після того, перевіряє наступний символ відносно `pattern:.*?`, і так далі... доки він нарешті доходить до `match:" class="doc">`. -But the problem is: that's already beyond the link ``, in another tag `

`. Not what we want. +Але ось де проблема: він вже вийшов поза посилання `` в інший тег `

`. Зовсім не те. -Here's the picture of the match aligned with the text: +Ось візуалізація збігу поруч з текстом: ```html ...

``` -So, we need the pattern to look for ``, but both greedy and lazy variants have problems. +Тож, патерн має шукати ``, але що жадібний, що лінивий варіанти мають проблеми. -The correct variant can be: `pattern:href="[^"]*"`. It will take all characters inside the `href` attribute till the nearest quote, just what we need. +Правильним варіантом може бути: `pattern:href="[^"]*"`. Він обере всі символи всередині атрибуту `href` до найближчих закриваючих лапок, саме те, що нам потрібно. -A working example: +Коректний приклад: ```js run let str1 = '......

...'; let str2 = '...... ...'; let regexp = //g; -// Works! -alert( str1.match(regexp) ); // null, no matches, that's correct +// Працює! +alert( str1.match(regexp) ); // null, збігів нема, все правильно alert( str2.match(regexp) ); // , ``` -## Summary +## Підсумки -Quantifiers have two modes of work: +Квантифікатори мають два режими роботи: -Greedy -: By default the regular expression engine tries to repeat the quantified character 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. +Жадібний +: За замовчуванням, рушій регулярного виразу намагається повторити квантифікований символ максимально можливу кількість разів. Для прикладу, `pattern:\d+` обирає всі можливі цифри. Коли продовжити цей процес неможливо (більше нема цифр/кінець рядку), тоді продовжується пошук збігу для решти патерну. Якщо збігу нема, він зменшує кількість повторень (повертається) та пробує наново. -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 quantified character. +Лінивий +: Включається знаком питання `pattern:?` після квантифікатору. Рушій намагається знайти збіг решти патерну перед кожним повторенням квантифікованого символу. -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, as in the pattern `pattern:"[^"]+"`. +Як бачимо, лінивий режим не є "панацеєю" від жадібного пошуку. Як альтернативу розглядають "добре налаштований" жадібний пошук, з виключенням, як в патерні `pattern:"[^"]+"`. From 60c0824928d6580b451d0907bdaf1142504128ba Mon Sep 17 00:00:00 2001 From: Stanislav Date: Tue, 22 Aug 2023 12:50:49 +0300 Subject: [PATCH 3/5] Apply suggestions from code review --- .../10-regexp-greedy-and-lazy/1-lazy-greedy/solution.md | 2 +- .../3-find-html-comments/solution.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/9-regular-expressions/10-regexp-greedy-and-lazy/1-lazy-greedy/solution.md b/9-regular-expressions/10-regexp-greedy-and-lazy/1-lazy-greedy/solution.md index d59edba55..35074dab1 100644 --- a/9-regular-expressions/10-regexp-greedy-and-lazy/1-lazy-greedy/solution.md +++ b/9-regular-expressions/10-regexp-greedy-and-lazy/1-lazy-greedy/solution.md @@ -3,4 +3,4 @@ Для початку, лінивий `pattern:\d+?` намагається взяти мінімальну можливу кількість цифр, але він має дійти до пробілу, тож обирається `match:123`. -Далі, другий `\d+?` обере лише одну тему, бо цього достатньо. +Далі, другий `\d+?` обере лише одну цифру, бо цього достатньо. diff --git a/9-regular-expressions/10-regexp-greedy-and-lazy/3-find-html-comments/solution.md b/9-regular-expressions/10-regexp-greedy-and-lazy/3-find-html-comments/solution.md index 184954114..cfa11dc25 100644 --- a/9-regular-expressions/10-regexp-greedy-and-lazy/3-find-html-comments/solution.md +++ b/9-regular-expressions/10-regexp-greedy-and-lazy/3-find-html-comments/solution.md @@ -1,6 +1,6 @@ -Нам потрібно знайти початок коментарю `match:`. +Нам потрібно знайти початок коментарю `match:`. -Прийнятним є варіант `pattern:` -- лінивий квантифікатор зупиняє крапку прямо перед `match:-->`. Нам також треба додати прапор `pattern:s`, аби крапка включала символи нового рядку. +Прийнятним є варіант `pattern:` -- лінивий квантифікатор зупиняє крапку (будь-який символ, за винятком символу нового рядку) прямо перед `match:-->`. Нам також треба додати прапорець `pattern:s`, аби крапка включала символи нового рядку. Інакше коментарі на кілька рядків не знаходитимуться: From fa8e2d8af47335b88b1dc84da12cf79fc4c83479 Mon Sep 17 00:00:00 2001 From: Stanislav Date: Tue, 22 Aug 2023 13:48:58 +0300 Subject: [PATCH 4/5] Apply suggestions from code review --- .../10-regexp-greedy-and-lazy/article.md | 86 +++++++++---------- 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/9-regular-expressions/10-regexp-greedy-and-lazy/article.md b/9-regular-expressions/10-regexp-greedy-and-lazy/article.md index aa73659d0..638fa584f 100644 --- a/9-regular-expressions/10-regexp-greedy-and-lazy/article.md +++ b/9-regular-expressions/10-regexp-greedy-and-lazy/article.md @@ -6,9 +6,9 @@ Візьмемо за приклад наступну задачу. -Ми маємо текст та хочемо замінити всі лапки `"..."` на французькі лапки : `«...»`. Їм віддають перевагу для типографії у багатьох країнах. +Ми маємо текст та хочемо замінити всі лапки `"..."` на французькі лапки: `«...»`. Їм віддають перевагу у типографії у багатьох країнах. -Наприклад: `"Hello, world"` має перетворитись на `«Hello, world»`. Існують й інші види лапок, як-то `„Witam, świat!”` (польські) or `「你好,世界」` (китайські), але для нашої задачі виберемо `«...»`. +Наприклад: `"Hello, world"` має перетворитись на `«Hello, world»`. Існують й інші види лапок, як-то `„Witam, świat!”` (польські) або `「你好,世界」` (китайські), але для нашої задачі виберемо `«...»`. Для початку, знайдемо рядки в лапках, аби потім їх замінити. @@ -26,49 +26,49 @@ alert( str.match(regexp) ); // "witch" and her "broom" ...Як бачимо, вираз працює не так, як очікувалось! -Замість двох збігів `match:" witch "` та `match:" broom"`, він знайшов один: `match:"witch" and her "broom"`. +Замість двох збігів `match:"witch"` та `match:"broom"`, він знайшов один: `match:"witch" and her "broom"`. -Про це можна сказати "жадібність – причина всіх бід". +Про це можна сказати "жадібність -- причина всіх бід". ## Жадібний пошук -Аби знайти збіг, рушій регулярного виразу використовує наступний алгоритм: +Аби знайти збіг, рушій регулярних виразів використовує наступний алгоритм: - Для кожної позиції в рядку - Спробувати виявити збіг на цій позиції. - - Якщо збігу нема, перейти до наступної позиції. + - Якщо збігу немає, перейти до наступної позиції. -Ці загальні фрази не пояснюють, чому регулярний вираз працює неправильно, тож вточнимо, як пошук працює для патерну `pattern:".+"`. +Ці загальні фрази не пояснюють, чому регулярний вираз працює неправильно, тож розберемо, як пошук працює для шаблону `pattern:".+"`. -1. Першим символом патерну є одна з лапок `pattern:"`. +1. Першим символом шаблону є одна з лапок `pattern:"`. Рушій регулярного виразу намагається знайти його на нульовій позиції вихідного рядку `subject:a "witch" and her "broom" is one`, але бачить `subject:a`, тож одразу зрозуміло, що збігу нема. - Йдемо далі: бере наступну позицію рядку та намагається на ній знайти перший символ патерну, знову невдача, але, нарешті, необхідний символ знаходиться на третій позиції: + Йдемо далі: бере наступну позицію рядку та намагається на ній знайти перший символ шаблону, знову невдача, але, нарешті, необхідний символ знаходиться на третій позиції: ![](witch_greedy1.svg) -2. Першу з лапок виявлено, після цього рушій намагається знайти збіг для решти патерну. Він намагається зрозуміти, чи відповідає решта рядку `pattern:.+"`. +2. Першу з лапок виявлено, після цього рушій намагається знайти збіг для решти шаблону. Він намагається зрозуміти, чи відповідає решта рядка `pattern:.+"`. - IВ нашому випадку, наступний символ патерну - це `pattern:.` (крапка). Він вказує на "будь-який символ, за винятком символу нового рядку", тож наступна літера рядку `match:'w'` підходить під опис: + В нашому випадку, наступний символ шаблону -- це `pattern:.` (крапка). Він вказує на "будь-який символ, за винятком символу нового рядку", тож наступна літера рядку `match:'w'` підходить під опис: ![](witch_greedy2.svg) -3. Після цього, дія крапки повторюється через наявність квантифікатору `pattern:.+`. Рушій регулярного виразу додає до збігу символи один за одним. +3. Після цього, дія крапки повторюється через наявність квантифікатору `pattern:.+`. Рушій регулярних виразів додає до збігу символи один за одним. ...До якого моменту? Крапка приймає усі символи, таким чином зупиняючись тільки досягнувши кінця рядку: ![](witch_greedy3.svg) -4. Тепер рушій завершив повтори `pattern:.+` та намагається знайти наступний символ патерну – другу з лапок `pattern:"`. Але виникає проблема: рядок закінчився, символів більше нема! +4. Тепер рушій завершив повтори `pattern:.+` та намагається знайти наступний символ шаблону -- другу закриваючу лапку `pattern:"`. Але виникає проблема: рядок закінчився, символів більше немає! - Рушій регулярного виразу розуміє, що додав забагато `pattern:.+` та починає *повернення*. + Рушій регулярних виразів розуміє, що взяв забагато `pattern:.+` та починає *повернення*. - Іншими словами, він скорочує збіг для квантифікатора на один символ: + Іншими словами, він скорочує збіг для квантифікатора по одному символу: ![](witch_greedy4.svg) - Після цього, рушій припускає, що `pattern:.+` завершується одним символом раніше кінця рядку та намагається знайти збіг для решти патерну, починаючи з тієї позиції. + Після цього, рушій припускає, що `pattern:.+` завершується одним символом раніше кінця рядку та намагається знайти збіг для решти шаблону, починаючи з тієї позиції. Якби друга з лапок була на цьому місці, то пошук завершився б, але останній символ `subject:'e'` не відповідає цілі пошуку. @@ -76,23 +76,23 @@ alert( str.match(regexp) ); // "witch" and her "broom" ![](witch_greedy5.svg) - Друга з лапок `pattern:'"'` не співпадає з `subject:'n'`. + Друга закриваюча лапка `pattern:'"'` не співпадає з `subject:'n'`. -6. Рушій продовжує процес повернення: число повторів `pattern:'.'` зменшується доти, доки решта патерну (в цьому випадку, `pattern:'"'`) не збігається: +6. Рушій продовжує процес повернення: число повторів `pattern:'.'` зменшується доти, доки решта шаблону (в цьому випадку, `pattern:'"'`) не збігається: ![](witch_greedy6.svg) 7. Збіг знайдено. -8. Отож, першим збігом буде:"witch" and her "broom"`. Якщо регулярний вираз має прапор `pattern:g`, тоді пошук продовжиться з кінця першого збігу. Решта рядку `subject:is one` не містить лапок, тож інших збігів не буде. +8. Отож, першим збігом буде:"witch" and her "broom"`. Якщо регулярний вираз має прапорець `pattern:g`, тоді пошук продовжиться з кінця першого збігу. Решта рядку `subject:is one` не містить лапок, тож інших збігів не буде. -Можливо, це протирічить нашим очікуванням, але так вже воно працює. +Напевно, це не те, чого ми очікували, але так вже воно працює. -**В жадібному режимі (за замовчуванням) квантифікований символ повторюється максимально можливу кількість разів.** +**В жадібному режимі (типово) квантифікований символ повторюється максимально можливу кількість разів.** -Рушій регулярного виразу додає до збігу всі можливі символи для `pattern:.+`, а потім зменшує результат посимвольно, якщо решта патерну не знаходить збігу. +Рушій регулярного виразу додає до збігу всі можливі символи для `pattern:.+`, а потім зменшує результат посимвольно, якщо решта шаблону не збігається. -Наша таска потребує іншого підходу. Тут може стати в пригоді лінивий режим. +Наша задача потребує іншого підходу. Тут може стати в нагоді лінивий режим. ## Лінивий режим @@ -114,7 +114,7 @@ alert( str.match(regexp) ); // "witch", "broom" Аби чітко побачити різницю, відслідкуємо процес пошуку покроково. -1. Перший крок той самий: знаходимо початок патерну `pattern:'"'` на третій позиції: +1. Перший крок той самий: знаходимо початок шаблону `pattern:'"'` на третій позиції: ![](witch_greedy1.svg) @@ -122,17 +122,17 @@ alert( str.match(regexp) ); // "witch", "broom" ![](witch_greedy2.svg) -3. З цього моменту пошук йде іншим шляхом. Для `pattern:+?` включений лінивий режим, тож тепер рушій більше не намагається знайти збіг для крапки, зупиняється та намагається знайти збіг для решти патерну `pattern:'"'`: +3. З цього моменту пошук йде іншим шляхом. Для `pattern:+?` включений лінивий режим, тож тепер рушій більше не намагається знайти збіг для крапки, зупиняється та намагається знайти збіг для решти шаблону `pattern:'"'`: ![](witch_lazy3.svg) Якби на цьому місці була остання з лапок, тоді пошук закінчився б, але бачимо `'i'`, тож збігу немає. -4. Далі, рушій регулярного виразу збільшує кількість повторів для крапки та ще раз проводить пошук: +4. Далі, рушій регулярних виразів збільшує кількість повторів для крапки та ще раз проводить пошук: ![](witch_lazy4.svg) Знову невдача, тож кількість повторів крок за кроком збільшується... -5. ...До моменту знаходження збігу для решти патерну: +5. ...До моменту знаходження збігу для решти шаблону: ![](witch_lazy5.svg) @@ -140,7 +140,7 @@ alert( str.match(regexp) ); // "witch", "broom" ![](witch_lazy6.svg) -В цьому прикладі, ми побачили, як працює лінивий режим для `pattern:+?`. Квантифікатори `pattern:*?` та `pattern:??` працюють за схожою схемою – рушій регулярного виразу збільшує кількість повторень, тільки якщо решта патерну не знаходить збігу на поточній позиції. +В цьому прикладі, ми побачили, як працює лінивий режим для `pattern:+?`. Квантифікатори `pattern:*?` та `pattern:??` працюють за схожою схемою -- рушій регулярних виразів збільшує кількість повторень, тільки якщо решта шаблону не знаходить збігу на поточній позиції. **Лінивий режим можна включити лише за допомогою `?`.** @@ -152,20 +152,20 @@ alert( str.match(regexp) ); // "witch", "broom" alert( "123 456".match(/\d+ \d+?/) ); // 123 4 ``` -1. Патерн `pattern:\d+` намагається додати в збіг якомога більше цифр (жадібний режим), тож він знаходить `match:123` та зупиняється, тому що наступним йде пробіл `pattern:' '`. -2. Далі в патерні працює пробіл, він бачить збіг. -3. Після цього, маємо `pattern:\d+?`. Квантифікатор в лінивому режимі, тож він знаходить одну цифру `match:4` та, починаючи з цієї позиції, переходить до перевірки на збіг для решти патерну. +1. Шаблон `pattern:\d+` намагається додати в збіг якомога більше цифр (жадібний режим), тож він знаходить `match:123` та зупиняється, тому що наступним йде пробіл `pattern:' '`. +2. Далі в шаблоні працює пробіл, відбувається збіг. +3. Після цього, маємо `pattern:\d+?`. Квантифікатор в лінивому режимі, тож він знаходить одну цифру `match:4` та, починаючи з цієї позиції, переходить до перевірки на збіг для решти шаблону. - ...Але після `pattern:\d+?` в патерні нічого не залишилось. + ...Але після `pattern:\d+?` в шаблоні нічого не залишилось. - Лінивий режим не повторює нічого без потреби. Патерн завершився, а з ним і наша робота. Ми знайшли збіг `match:123 4`. + Лінивий режим не повторює нічого без потреби. Шаблон завершився, а з ним і наша робота. Ми знайшли збіг `match:123 4`. ```smart header="Optimizations" Сучасні рушії регулярних виразів можуть оптимізовувати внутрішні алгоритми задля швидшої роботи. Тож їх алгоритм роботи може трішки відрізнятись від щойно описаного. Але, для розуміння принципу побудови та роботи регулярних виразів, нам все це знати не обов’язково. Вони використовуються виключно внутрішньо для оптимізації. -Складні регулярні вирази погано піддаються оптимізації, тож, взагалі-то, пошук може працювати й за описом. +Складні регулярні вирази погано піддаються оптимізації, тож пошук працюватиме саме так, як описано вище. ``` ## Інший підхід @@ -184,7 +184,7 @@ alert( str.match(regexp) ); // "witch", "broom" Регулярний вираз `pattern:"[^"]+"` дає правильні результати, бо шукає першу з лапок `pattern:'"'`, за якою слідують один чи більше символів (не лапок) `pattern:[^"]` та друга з лапок в кінці. -Коли рушій регулярного виразу шукає `pattern:[^"]+`, він припиняє повторення, як тільки зустрічає другу з лапок, і все. +Коли рушій регулярних виразів шукає `pattern:[^"]+`, він припиняє повторення, як тільки зустрічає другу з лапок, на цьому все. Зверніть увагу, цей спосіб не замінює ліниві квантифікатори! @@ -226,7 +226,7 @@ alert( str.match(regexp) ); // ... ... ``` -Змінимо патерн, зробивши квантифікатор `pattern:.*?` лінивим: +Змінимо шаблон, зробивши квантифікатор `pattern:.*?` лінивим: ```js run let str = '...... ...'; @@ -253,14 +253,14 @@ let regexp = //g; alert( str.match(regexp) ); // ...

``` -Маємо провал. Збіг не обмежується посиланням, а містить також купу тексту, разом з ``. +Тепер він не працює, як ми хотіли. Збіг не обмежується посиланням, а містить також купу тексту, разом з ``. Чому? Ось процес виконання: 1. Спочатку, регулярний вираз знаходить початок посилання `match:` (нема). +2. Далі, він шукає `pattern:.*?`: бере один символ (ліниво!), перевіряє наявність збігу для `pattern:" class="doc">` (немає). 3. Після того, перевіряє наступний символ відносно `pattern:.*?`, і так далі... доки він нарешті доходить до `match:" class="doc">`. Але ось де проблема: він вже вийшов поза посилання `` в інший тег `

`. Зовсім не те. @@ -272,7 +272,7 @@ alert( str.match(regexp) ); // ...

...

``` -Тож, патерн має шукати ``, але що жадібний, що лінивий варіанти мають проблеми. +Тож, шаблон має шукати ``, але що жадібний, що лінивий варіанти мають проблеми. Правильним варіантом може бути: `pattern:href="[^"]*"`. Він обере всі символи всередині атрибуту `href` до найближчих закриваючих лапок, саме те, що нам потрібно. @@ -284,7 +284,7 @@ let str2 = '...... ...'; let regexp = //g; // Працює! -alert( str1.match(regexp) ); // null, збігів нема, все правильно +alert( str1.match(regexp) ); // null, збігів немає, все правильно alert( str2.match(regexp) ); // , ``` @@ -293,9 +293,9 @@ alert( str2.match(regexp) ); // , Date: Tue, 22 Aug 2023 13:49:43 +0300 Subject: [PATCH 5/5] Apply suggestions from code review --- 9-regular-expressions/10-regexp-greedy-and-lazy/article.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/9-regular-expressions/10-regexp-greedy-and-lazy/article.md b/9-regular-expressions/10-regexp-greedy-and-lazy/article.md index 638fa584f..a8929c241 100644 --- a/9-regular-expressions/10-regexp-greedy-and-lazy/article.md +++ b/9-regular-expressions/10-regexp-greedy-and-lazy/article.md @@ -42,7 +42,7 @@ alert( str.match(regexp) ); // "witch" and her "broom" 1. Першим символом шаблону є одна з лапок `pattern:"`. - Рушій регулярного виразу намагається знайти його на нульовій позиції вихідного рядку `subject:a "witch" and her "broom" is one`, але бачить `subject:a`, тож одразу зрозуміло, що збігу нема. + Рушій регулярних виразів намагається знайти його на нульовій позиції вихідного рядку `subject:a "witch" and her "broom" is one`, але бачить `subject:a`, тож вважає, що збігу немає. Йдемо далі: бере наступну позицію рядку та намагається на ній знайти перший символ шаблону, знову невдача, але, нарешті, необхідний символ знаходиться на третій позиції: