From f8048b57a681d5d37703e44ef7e21b6d8ad4995a Mon Sep 17 00:00:00 2001 From: SamGreenberg Date: Fri, 20 Jan 2023 23:57:48 +0300 Subject: [PATCH 01/23] Catastrophic backtracking --- .../article.md | 218 +++++++++--------- 1 file changed, 109 insertions(+), 109 deletions(-) diff --git a/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md b/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md index b6e1054fb..361d093ab 100644 --- a/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md +++ b/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md @@ -1,83 +1,83 @@ -# Catastrophic backtracking +# Катастрофічний пошук з поверненням -Some regular expressions are looking simple, but can execute a veeeeeery long time, and even "hang" the JavaScript engine. +Деякі регулярні вирази виглядають простими, але можуть виконуватись дууууууже довгий час, та навіть змусити "зависнути" двигун JavaScript. -Sooner or later most developers occasionally face such behavior. The typical symptom -- a regular expression works fine sometimes, but for certain strings it "hangs", consuming 100% of CPU. +Рано чи пізно більшість розробників стикаються з подібною ситуацією. Типовий показник – часом регулярний вираз працює добре, але на конкретних рядках він "зависає", споживаючи 100% CPU. -In such case a web-browser suggests to kill the script and reload the page. Not a good thing for sure. +В такому випадку браузер пропонує зупинити скрипт та перезавантажити сторінку. Не найкращий розвиток подій. -For server-side JavaScript such a regexp may hang the server process, that's even worse. So we definitely should take a look at it. +Навіть гірше, для серверного JavaScript подібний регулярний вираз може спричинити зависання серверного процесу. Тож варто звернути увагу на цей момент. -## Example +## Приклад -Let's say we have a string, and we'd like to check if it consists of words `pattern:\w+` with an optional space `pattern:\s?` after each. +Скажімо, ми маємо рядок і хотіли б перевірити його на наявність слів `pattern:\w+` з необов’язковим пробілом `pattern:\s?` після кожного. -An obvious way to construct a regexp would be to take a word followed by an optional space `pattern:\w+\s?` and then repeat it with `*`. +Очевидний спосіб сконструювати регулярний вираз – взяти слово, за яким йде необов’язковий пробіл `pattern:\w+\s?`, та прописати повтор `*`. -That leads us to the regexp `pattern:^(\w+\s?)*$`, it specifies zero or more such words, that start at the beginning `pattern:^` and finish at the end `pattern:$` of the line. +Це приведе нас до регулярного виразу `pattern:^(\w+\s?)*$`, який описує 0+ таких слів, починається з `pattern:^` та закінчується на `pattern:$`. -In action: +На прикладі: ```js run let regexp = /^(\w+\s?)*$/; -alert( regexp.test("A good string") ); // true -alert( regexp.test("Bad characters: $@#") ); // false +alert( regexp.test("Хороший рядок") ); // true +alert( regexp.test("Погані символи: $@#") ); // false ``` -The regexp seems to work. The result is correct. Although, on certain strings it takes a lot of time. So long that JavaScript engine "hangs" with 100% CPU consumption. +Регулярний вираз наче працює. Результат коректний. Та все ж, певні рядки забирають багато часу. Достатньо, аби двигун JavaScript "зависнув", споживаючи 100% CPU. -If you run the example below, you probably won't see anything, as JavaScript will just "hang". A web-browser will stop reacting on events, the UI will stop working (most browsers allow only scrolling). After some time it will suggest to reload the page. So be careful with this: +Якщо виконати приклад нижче, можливо, ви нічого не побачите, бо JavaScript просто "зависне". Браузер перестане реагувати на події, UI перестане працювати (більшість браузерів лишають лише можливість прокрутки). За деякий час, вам запропонують перезавантажити сторінку. Тож будьте обережними: ```js run let regexp = /^(\w+\s?)*$/; -let str = "An input string that takes a long time or even makes this regexp hang!"; +let str = "Введений рядок, який оброблятиметься довго або навіть спровокує зависання регулярного виразу!"; -// will take a very long time +// займе дуже багато часу alert( regexp.test(str) ); ``` -To be fair, let's note that some regular expression engines can handle such a search effectively, for example V8 engine version starting from 8.8 can do that (so Google Chrome 88 doesn't hang here), while Firefox browser does hang. +Чесно кажучи, відмітимо, що деякі двигуни регулярних виразів можуть провести ефективно провести подібний пошук. Наприклад, версія двигуну V8 починаючи з 8.8 здатна на це(тобто Google Chrome 88 не зависне), а Firefox все ж матиме проблеми. -## Simplified example +## Спрощений приклад -What's the matter? Why does the regular expression hang? +В чому ж справа? Чому регулярний вираз спричинює зависання? -To understand that, let's simplify the example: remove spaces `pattern:\s?`. Then it becomes `pattern:^(\w+)*$`. +Аби це зрозуміти, спростимо приклад: приберемо пробіли `pattern:\s?`, отримаємо `pattern:^(\w+)*$`. -And, to make things more obvious, let's replace `pattern:\w` with `pattern:\d`. The resulting regular expression still hangs, for instance: +Також, для більшої очевидності, замінимо `pattern:\w` на `pattern:\d`. Отриманий регулярний вираз все одно зависає: ```js run let regexp = /^(\d+)*$/; let str = "012345678901234567890123456789z"; -// will take a very long time (careful!) +// займе дуже багато часу (обережніше!) alert( regexp.test(str) ); ``` -So what's wrong with the regexp? +То що з ним не так? -First, one may notice that the regexp `pattern:(\d+)*` is a little bit strange. The quantifier `pattern:*` looks extraneous. If we want a number, we can use `pattern:\d+`. +По-перше, помітно, що регулярний вираз `pattern:(\d+)*` є трішки дивним. Квантифікатор `pattern:*` виглядає недоречним. Якщо нам потрібно число, можна використати `pattern:\d+`. -Indeed, the regexp is artificial; we got it by simplifying the previous example. But the reason why it is slow is the same. So let's understand it, and then the previous example will become obvious. +Дійсно, регулярний вираз is artificial, отриманий після спрощення попереднього прикладу. Але причина повільної роботи лишається тією ж. Зрозуміємо її джерело, тоді й попередній приклад стане очевидним. -What happens during the search of `pattern:^(\d+)*$` in the line `subject:123456789z` (shortened a bit for clarity, please note a non-digit character `subject:z` at the end, it's important), why does it take so long? +Що коїться під час пошуку `pattern:^(\d+)*$` в рядку `subject:123456789z` (трішки скорочений для ясності, відмітьте нецифровий символ `subject:z` наприкінці, він важливий), чому він триває так довго? -Here's what the regexp engine does: +Ось що робить двигун регулярних виразів: -1. First, the regexp engine tries to find the content of the parentheses: the number `pattern:\d+`. The plus `pattern:+` is greedy by default, so it consumes all digits: +1. Для початку, двигун регулярного виразу намагається знайти вміст дужок: число `pattern:\d+`. За замовчуванням, режим `pattern:+` є жадібним, тому він поглинає всі цифри: ``` \d+....... (123456789)z ``` - After all digits are consumed, `pattern:\d+` is considered found (as `match:123456789`). + Після поглинання всіх цифр, `pattern:\d+` вважається знайденим (як `match:123456789`). - Then the star quantifier `pattern:(\d+)*` applies. But there are no more digits in the text, so the star doesn't give anything. + Далі, застосовується квантифікатор `pattern:(\d+)*`. В тексті не залишилось цифр, тож зірка нічого не дає. - The next character in the pattern is the string end `pattern:$`. But in the text we have `subject:z` instead, so there's no match: + Наступний символ патерну – кінець рядку `pattern:$`. Але в тексті маємо `subject:z`, тож збігу немає: ``` X @@ -85,16 +85,16 @@ Here's what the regexp engine does: (123456789)z ``` -2. As there's no match, the greedy quantifier `pattern:+` decreases the count of repetitions, backtracks one character back. +2. Без збігу, жадібний квантифікатор `pattern:+` зменшує кількість повторів та повертається на один символ назад. - Now `pattern:\d+` takes all digits except the last one (`match:12345678`): + Тепер `pattern:\d+` приймає усі цифри, окрім останньої(`match:12345678`): ``` \d+....... (12345678)9z ``` -3. Then the engine tries to continue the search from the next position (right after `match:12345678`). +3. Потім двигун намагається продовжити пошук з наступної позиції (одразу після `match:12345678`). - The star `pattern:(\d+)*` can be applied -- it gives one more match of `pattern:\d+`, the number `match:9`: + Можна застосувати `pattern:(\d+)*` -- це дасть ще один збіг для `pattern:\d+`, число `match:9`: ``` @@ -102,7 +102,7 @@ Here's what the regexp engine does: (12345678)(9)z ``` - The engine tries to match `pattern:$` again, but fails, because it meets `subject:z` instead: + Двигун невдало намагається знову шукати збіг для `pattern:$`, натомість зустрічає `subject:z`: ``` X @@ -111,11 +111,11 @@ Here's what the regexp engine does: ``` -4. There's no match, so the engine will continue backtracking, decreasing the number of repetitions. Backtracking generally works like this: the last greedy quantifier decreases the number of repetitions until it reaches the minimum. Then the previous greedy quantifier decreases, and so on. +4. Збігу нема, тому двигун продовжить пошук з поверненням, зменшуючи кількість повторень. Зазвичай, це працює наступним чином: останній жадібний квантифікатор зменшує кількість повторень доти, доки не досягнутий мінімум. Потім спрацьовує попередній жадібний квантифікатор, і так далі. - All possible combinations are attempted. Here are their examples. + Ми спробували всі можливі комбінації. Далі – їх приклади. - The first number `pattern:\d+` has 7 digits, and then a number of 2 digits: + Перше число `pattern:\d+` має 7 цифр та двозначне число опісля: ``` X @@ -123,7 +123,7 @@ Here's what the regexp engine does: (1234567)(89)z ``` - The first number has 7 digits, and then two numbers of 1 digit each: + Перше число має 7 цифр та два одноцифрових числа опісля: ``` X @@ -131,7 +131,7 @@ Here's what the regexp engine does: (1234567)(8)(9)z ``` - The first number has 6 digits, and then a number of 3 digits: + Перше число має 6 цифр та трицифрове число за ними: ``` X @@ -139,7 +139,7 @@ Here's what the regexp engine does: (123456)(789)z ``` - The first number has 6 digits, and then 2 numbers: + Перше число має 6 цифр та два числа за ними: ``` X @@ -147,22 +147,22 @@ Here's what the regexp engine does: (123456)(78)(9)z ``` - ...And so on. + ...І так далі. -There are many ways to split a sequence of digits `123456789` into numbers. To be precise, there are 2n-1, where `n` is the length of the sequence. +Існує багато шляхів поділу послідовності цифр `123456789` на номери. Казати точно, усього 2n-1, де `n` - довжина послідовності. -- For `123456789` we have `n=9`, that gives 511 combinations. -- For a longer sequence with `n=20` there are about one million (1048575) combinations. -- For `n=30` - a thousand times more (1073741823 combinations). +- Для `123456789`, маємо `n=9`, що дає 511 комбінацій. +- Довша послідовність `n=20` дасть приблизно мільйон (1048575) комбінацій. +- Для `n=30` - в тисячу разів більше (1073741823 комбінацій). -Trying each of them is exactly the reason why the search takes so long. +Проходження кожною з них і є причиною повільного пошуку. -## Back to words and strings +## Повертаючись до слів та рядків -The similar thing happens in our first example, when we look for words by pattern `pattern:^(\w+\s?)*$` in the string `subject:An input that hangs!`. +Подібне відбувається в нашому першому прикладі, коли ми шукаємо слова за патерном `pattern:^(\w+\s?)*$` в рядку `subject:Рядок, що висне!`. -The reason is that a word can be represented as one `pattern:\w+` or many: +Причина в тому, що слово може бути представлене як один чи кілька `pattern:\w+`: ``` (input) @@ -172,63 +172,63 @@ The reason is that a word can be represented as one `pattern:\w+` or many: ... ``` -For a human, it's obvious that there may be no match, because the string ends with an exclamation sign `!`, but the regular expression expects a wordly character `pattern:\w` or a space `pattern:\s` at the end. But the engine doesn't know that. +З точки зору людини, збігу не може бути, бо рядок закінчується знаком `!`, але регулярний вираз в кінці очікує символ “слова” `pattern:\w` або пробіл `pattern:\s`. Але двигун цього не знає. -It tries all combinations of how the regexp `pattern:(\w+\s?)*` can "consume" the string, including variants with spaces `pattern:(\w+\s)*` and without them `pattern:(\w+)*` (because spaces `pattern:\s?` are optional). As there are many such combinations (we've seen it with digits), the search takes a lot of time. +Він перебирає всі комбінації "поглинання" рядку регулярним виразом `pattern:(\w+\s?)*`, включаючи варіанти з пробілами `pattern:(\w+\s)*` та без `pattern:(\w+)*` (бо пробіли `pattern:\s?` необов’язкові). Пошук є тривалим через велику кількість комбінацій (як на прикладі цифр). -What to do? +Що робити? -Should we turn on the lazy mode? +Чи варто включати лінивий режим? -Unfortunately, that won't help: if we replace `pattern:\w+` with `pattern:\w+?`, the regexp will still hang. The order of combinations will change, but not their total count. +Нажаль, це не допоможе: якщо замінити `pattern:\w+` на `pattern:\w+?`, регулярний вираз все одно висне. Зміниться порядок комбінацій, але не загальна кількість. -Some regular expression engines have tricky tests and finite automations that allow to avoid going through all combinations or make it much faster, but most engines don't, and it doesn't always help. +Деякі двигуни регулярних виразів мають хитро побудовані тести та скінченні автомати, що дозволяють оминути проходження всіма комбінаціями або пришвидшити, але це стосується меншості двигунів та не завжди допомагає. -## How to fix? +## Як це виправити? -There are two main approaches to fixing the problem. +Існує два основних підходи до вирішення проблеми. -The first is to lower the number of possible combinations. +Перший – знизити кількість можливих комбінацій. -Let's make the space non-optional by rewriting the regular expression as `pattern:^(\w+\s)*\w*$` - we'll look for any number of words followed by a space `pattern:(\w+\s)*`, and then (optionally) a final word `pattern:\w*`. +Зробимо пробіл обов’язковим, переписавши регулярний вираз як `pattern:^(\w+\s)*\w*$` - ми шукатимемо будь-яку кількість слів та пробіл опісля `pattern:(\w+\s)*`, далі (необов’язково) останнє слово `pattern:\w*`. -This regexp is equivalent to the previous one (matches the same) and works well: +Цей регулярний вираз рівноцінний попередньому (збіг той самий) та працює відмінно: ```js run let regexp = /^(\w+\s)*\w*$/; -let str = "An input string that takes a long time or even makes this regex hang!"; +let str = "Введений рядок, який оброблятиметься довго або навіть спровокує зависання регулярного виразу!"; alert( regexp.test(str) ); // false ``` -Why did the problem disappear? +Чому проблема зникла? -That's because now the space is mandatory. +Бо тепер пробіл є обов’язковим. -The previous regexp, if we omit the space, becomes `pattern:(\w+)*`, leading to many combinations of `\w+` within a single word +Попередній регулярний вираз, якщо не врахувати пробіл, стає `pattern:(\w+)*`, призводячи до багатьох комбінацій `\w+` посеред єдиного слова. -So `subject:input` could be matched as two repetitions of `pattern:\w+`, like this: +Тож `subject:input` може мати збіг у вигляді двох повторень `pattern:\w+`: ``` \w+ \w+ (inp)(ut) ``` -The new pattern is different: `pattern:(\w+\s)*` specifies repetitions of words followed by a space! The `subject:input` string can't be matched as two repetitions of `pattern:\w+\s`, because the space is mandatory. +Новий патерн інший: `pattern:(\w+\s)*` описує повторення слів, за якими йде пробіл! Рядок `subject:input` не буде збігом для двох повторень `pattern:\w+\s`, оскільки пробіл є обов’язковим. -The time needed to try a lot of (actually most of) combinations is now saved. +Час проходження великою кількістю (взагалі-то, більшістю) комбінацій зекономлено. -## Preventing backtracking +## Запобігання поверненню -It's not always convenient to rewrite a regexp though. In the example above it was easy, but it's not always obvious how to do it. +Все ж, не завжди зручно переписувати регулярний вираз. Попередній приклад був простим, інші бувають не надто очевидними. -Besides, a rewritten regexp is usually more complex, and that's not good. Regexps are complex enough without extra efforts. +До того ж, змінений регулярний вираз зазвичай більш складний, що не є добре. Регулярні вирази й так достатньо складні. -Luckily, there's an alternative approach. We can forbid backtracking for the quantifier. +На щастя, мається альтернативний підхід. Можна заборонити повернення для квантифікатора. -The root of the problem is that the regexp engine tries many combinations that are obviously wrong for a human. +Корінь проблеми в двигуні регулярного виразу, що пробує багато очевидно невірних (для людини) комбінацій. -E.g. in the regexp `pattern:(\d+)*$` it's obvious for a human, that `pattern:+` shouldn't backtrack. If we replace one `pattern:\d+` with two separate `pattern:\d+\d+`, nothing changes: +Наприклад, для людини очевидно, що в `pattern:(\d+)*$`, `pattern:+` не потребує пошуку з поверненням. Якщо замінити `pattern:\d+` на два окремих `pattern:\d+\d+`, нічого не зміниться: ``` \d+........ @@ -238,81 +238,81 @@ E.g. in the regexp `pattern:(\d+)*$` it's obvious for a human, that `pattern:+` (1234)(56789)! ``` -And in the original example `pattern:^(\w+\s?)*$` we may want to forbid backtracking in `pattern:\w+`. That is: `pattern:\w+` should match a whole word, with the maximal possible length. There's no need to lower the repetitions count in `pattern:\w+` or to split it into two words `pattern:\w+\w+` and so on. +Також, можливо, всередині початкового прикладу `pattern:^(\w+\s?)*$` ми б хотіли заборонити пошук з поверненням в `pattern:\w+`. Отже, `pattern:\w+` має знаходити збіг цілому слову, з максимально можливою довжиною. Нема потреби знижувати кількість повторень в `pattern:\w+`, ділити на два слова `pattern:\w+\w+`, і таке інше. -Modern regular expression engines support possessive quantifiers for that. Regular quantifiers become possessive if we add `pattern:+` after them. That is, we use `pattern:\d++` instead of `pattern:\d+` to stop `pattern:+` from backtracking. +Для цього, сучасні двигуни регулярних виразів підтримують присвійні квантифікатори. Звичайні квантифікатори стають присвійними, якщо після них додати `pattern:+`. Саме так, ми використаємо `pattern:\d++` замість `pattern:\d+`, аби зупинити пошук з поверненням для `pattern:+`. -Possessive quantifiers are in fact simpler than "regular" ones. They just match as many as they can, without any backtracking. The search process without backtracking is simpler. +Насправді, присвійні квантифікатори є простішими за "звичайні". Вони просто шукають стільки збігів, скільки можуть, без будь-якого повернення. Такий процес пошуку, звичайно, простіший. -There are also so-called "atomic capturing groups" - a way to disable backtracking inside parentheses. +Також існують так звані "атомні групи захоплення" – спосіб відключити повернення всередині дужок. -...But the bad news is that, unfortunately, in JavaScript they are not supported. +...Погані новини: нажаль, JavaScript їх не підтримує. -We can emulate them though using a "lookahead transform". +Замість них можна використати "перетворення переглядом вперед". -### Lookahead to the rescue! +### Вперед по допомогу! -So we've come to real advanced topics. We'd like a quantifier, such as `pattern:+` not to backtrack, because sometimes backtracking makes no sense. +Тож, ми підібрались до дійсно передової теми. Нам потрібен квантифікатор, як-то `pattern:+`, без пошуку з поверненням, тому що іноді це не має ніякого сенсу. -The pattern to take as many repetitions of `pattern:\w` as possible without backtracking is: `pattern:(?=(\w+))\1`. Of course, we could take another pattern instead of `pattern:\w`. +Патерн з максимальною кількістю повторів `pattern:\w` без повернення: `pattern:(?=(\w+))\1`. Звісно, можемо взяти інший патерн замість `pattern:\w`. -That may seem odd, but it's actually a very simple transform. +Здається дивним, але це дуже просте перетворення. -Let's decipher it: +Розберемо його: -- Lookahead `pattern:?=` looks forward for the longest word `pattern:\w+` starting at the current position. -- The contents of parentheses with `pattern:?=...` isn't memorized by the engine, so wrap `pattern:\w+` into parentheses. Then the engine will memorize their contents -- ...And allow us to reference it in the pattern as `pattern:\1`. +- Перегляд вперед `pattern:?=` шукає найдовше слово `pattern:\w+`, починаючи з поточної позиції. +- Вміст дужок з `pattern:?=...` не запам’ятовується двигуном, тож огорнемо дужками `pattern:\w+`. В цьому випадку, двигун запам’ятає вміст дужок. +- ...Це дозволяє нам посилатись на них всередині патерну: `pattern:\1`. -That is: we look ahead - and if there's a word `pattern:\w+`, then match it as `pattern:\1`. +Тобто, ми дивимось вперед – якщо там є слово `pattern:\w+`, то then match it as `pattern:\1`. -Why? That's because the lookahead finds a word `pattern:\w+` as a whole and we capture it into the pattern with `pattern:\1`. So we essentially implemented a possessive plus `pattern:+` quantifier. It captures only the whole word `pattern:\w+`, not a part of it. +Чому? Все через те, що перегляд вперед знаходить слово `pattern:\w+` повністю та ми беремо його в патерн разом з `pattern:\1`. Отож, по суті, ми застосовуємо присвійний квантифікатор `pattern:+`. Він охоплює все слово `pattern:\w+`, а не якусь частину. -For instance, in the word `subject:JavaScript` it may not only match `match:Java`, but leave out `match:Script` to match the rest of the pattern. +Для прикладу, в слові `subject:JavaScript` він не тільки знайде збіг `match:Java`, але й лишають `match:Script` для пошуку збігу з рештою патерну. -Here's the comparison of two patterns: +Порівняння двох патернів: ```js run alert( "JavaScript".match(/\w+Script/)); // JavaScript alert( "JavaScript".match(/(?=(\w+))\1Script/)); // null ``` -1. In the first variant `pattern:\w+` first captures the whole word `subject:JavaScript` but then `pattern:+` backtracks character by character, to try to match the rest of the pattern, until it finally succeeds (when `pattern:\w+` matches `match:Java`). -2. In the second variant `pattern:(?=(\w+))` looks ahead and finds the word `subject:JavaScript`, that is included into the pattern as a whole by `pattern:\1`, so there remains no way to find `subject:Script` after it. +1. В першому випадку, `pattern:\w+` спочатку бере ціле слово `subject:JavaScript`, але потім `pattern:+` символ за символом проводить пошук з поверненням, намагаючись знайти збіг для решти патерну доти, доки не досягне цілі (коли `pattern:\w+` відповідає `match:Java`). +2. В другому випадку, `pattern:(?=(\w+))` дивиться вперед та знаходить слово `subject:JavaScript`, повністю включене в патерн за допомогою `pattern:\1`, тож опісля нема ніякої можливості для пошуку `subject:Script`. -We can put a more complex regular expression into `pattern:(?=(\w+))\1` instead of `pattern:\w`, when we need to forbid backtracking for `pattern:+` after it. +Ми можемо помістити більш комплексний регулярний вираз у `pattern:(?=(\w+))\1` замість `pattern:\w`, коли нам потрібно заборонити пошук з поверненням для `pattern:+` після нього. ```smart -There's more about the relation between possessive quantifiers and lookahead in articles [Regex: Emulate Atomic Grouping (and Possessive Quantifiers) with LookAhead](http://instanceof.me/post/52245507631/regex-emulate-atomic-grouping-with-lookahead) and [Mimicking Atomic Groups](http://blog.stevenlevithan.com/archives/mimic-atomic-groups). +Більше про зв’язок між присвійними квантифікаторами та переглядом вперед в статтях [Regex: Emulate Atomic Grouping (and Possessive Quantifiers) with LookAhead](http://instanceof.me/post/52245507631/regex-emulate-atomic-grouping-with-lookahead) та [Mimicking Atomic Groups](http://blog.stevenlevithan.com/archives/mimic-atomic-groups). ``` -Let's rewrite the first example using lookahead to prevent backtracking: +Перепишемо перший приклад, використовуючи перегляд вперед для запобігання пошуку з поверненням: ```js run let regexp = /^((?=(\w+))\2\s?)*$/; -alert( regexp.test("A good string") ); // true +alert( regexp.test("Хороший рядок") ); // true -let str = "An input string that takes a long time or even makes this regex hang!"; +let str = "Введений рядок, який оброблятиметься довго або навіть спровокує зависання регулярного виразу!"; -alert( regexp.test(str) ); // false, works and fast! +alert( regexp.test(str) ); // false, працює швидко! ``` -Here `pattern:\2` is used instead of `pattern:\1`, because there are additional outer parentheses. To avoid messing up with the numbers, we can give the parentheses a name, e.g. `pattern:(?\w+)`. +Як бачимо, `pattern:\2` використовується замість `pattern:\1` через додаткові зовнішні дужки. To avoid messing up with the numbers, ми можемо назвати дужки, наприклад, `pattern:(?\w+)`. ```js run -// parentheses are named ?, referenced as \k +// дужки мають ім’я ?, на що посилається \k let regexp = /^((?=(?\w+))\k\s?)*$/; -let str = "An input string that takes a long time or even makes this regex hang!"; +let str = "Введений рядок, який оброблятиметься довго або навіть спровокує зависання регулярного виразу!"; alert( regexp.test(str) ); // false -alert( regexp.test("A correct string") ); // true +alert( regexp.test("Правильний рядок") ); // true ``` -The problem described in this article is called "catastrophic backtracking". +Проблема, описана в статті, має назву "катастрофічний пошук з поверненням". -We covered two ways how to solve it: -- Rewrite the regexp to lower the possible combinations count. -- Prevent backtracking. +Ми розглянули два шляхи її вирішення: +- Переписати регулярний вираз для зменшення кількості можливих комбінацій. +- Запобігти поверненню. \ No newline at end of file From 2decee6396fb717f4275322665f2a766e63d1d55 Mon Sep 17 00:00:00 2001 From: SamGreenberg Date: Sun, 22 Jan 2023 20:39:49 +0300 Subject: [PATCH 02/23] fix --- .../article.md | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md b/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md index 361d093ab..4845f2ffe 100644 --- a/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md +++ b/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md @@ -1,6 +1,6 @@ # Катастрофічний пошук з поверненням -Деякі регулярні вирази виглядають простими, але можуть виконуватись дууууууже довгий час, та навіть змусити "зависнути" двигун JavaScript. +Деякі регулярні вирази виглядають простими, але можуть виконуватись дууууууже довгий час, та навіть змусити "зависнути" рушій JavaScript. Рано чи пізно більшість розробників стикаються з подібною ситуацією. Типовий показник – часом регулярний вираз працює добре, але на конкретних рядках він "зависає", споживаючи 100% CPU. @@ -25,7 +25,7 @@ alert( regexp.test("Хороший рядок") ); // true alert( regexp.test("Погані символи: $@#") ); // false ``` -Регулярний вираз наче працює. Результат коректний. Та все ж, певні рядки забирають багато часу. Достатньо, аби двигун JavaScript "зависнув", споживаючи 100% CPU. +Регулярний вираз наче працює. Результат коректний. Та все ж, певні рядки забирають багато часу. Достатньо, аби рушій JavaScript "зависнув", споживаючи 100% CPU. Якщо виконати приклад нижче, можливо, ви нічого не побачите, бо JavaScript просто "зависне". Браузер перестане реагувати на події, UI перестане працювати (більшість браузерів лишають лише можливість прокрутки). За деякий час, вам запропонують перезавантажити сторінку. Тож будьте обережними: @@ -37,7 +37,7 @@ let str = "Введений рядок, який оброблятиметься alert( regexp.test(str) ); ``` -Чесно кажучи, відмітимо, що деякі двигуни регулярних виразів можуть провести ефективно провести подібний пошук. Наприклад, версія двигуну V8 починаючи з 8.8 здатна на це(тобто Google Chrome 88 не зависне), а Firefox все ж матиме проблеми. +Чесно кажучи, відмітимо, що деякі рушії регулярних виразів можуть провести ефективно провести подібний пошук. Наприклад, версія рушію V8 починаючи з 8.8 здатна на це(тобто Google Chrome 88 не зависне), а Firefox все ж матиме проблеми. ## Спрощений приклад @@ -64,9 +64,9 @@ alert( regexp.test(str) ); Що коїться під час пошуку `pattern:^(\d+)*$` в рядку `subject:123456789z` (трішки скорочений для ясності, відмітьте нецифровий символ `subject:z` наприкінці, він важливий), чому він триває так довго? -Ось що робить двигун регулярних виразів: +Ось що робить рушій регулярних виразів: -1. Для початку, двигун регулярного виразу намагається знайти вміст дужок: число `pattern:\d+`. За замовчуванням, режим `pattern:+` є жадібним, тому він поглинає всі цифри: +1. Для початку, рушій регулярного виразу намагається знайти вміст дужок: число `pattern:\d+`. За замовчуванням, режим `pattern:+` є жадібним, тому він поглинає всі цифри: ``` \d+....... @@ -92,7 +92,7 @@ alert( regexp.test(str) ); \d+....... (12345678)9z ``` -3. Потім двигун намагається продовжити пошук з наступної позиції (одразу після `match:12345678`). +3. Потім рушій намагається продовжити пошук з наступної позиції (одразу після `match:12345678`). Можна застосувати `pattern:(\d+)*` -- це дасть ще один збіг для `pattern:\d+`, число `match:9`: @@ -102,7 +102,7 @@ alert( regexp.test(str) ); (12345678)(9)z ``` - Двигун невдало намагається знову шукати збіг для `pattern:$`, натомість зустрічає `subject:z`: + Рушій невдало намагається знову шукати збіг для `pattern:$`, натомість зустрічає `subject:z`: ``` X @@ -111,7 +111,7 @@ alert( regexp.test(str) ); ``` -4. Збігу нема, тому двигун продовжить пошук з поверненням, зменшуючи кількість повторень. Зазвичай, це працює наступним чином: останній жадібний квантифікатор зменшує кількість повторень доти, доки не досягнутий мінімум. Потім спрацьовує попередній жадібний квантифікатор, і так далі. +4. Збігу нема, тому рушій продовжить пошук з поверненням, зменшуючи кількість повторень. Зазвичай, це працює наступним чином: останній жадібний квантифікатор зменшує кількість повторень доти, доки не досягнутий мінімум. Потім спрацьовує попередній жадібний квантифікатор, і так далі. Ми спробували всі можливі комбінації. Далі – їх приклади. @@ -172,7 +172,7 @@ alert( regexp.test(str) ); ... ``` -З точки зору людини, збігу не може бути, бо рядок закінчується знаком `!`, але регулярний вираз в кінці очікує символ “слова” `pattern:\w` або пробіл `pattern:\s`. Але двигун цього не знає. +З точки зору людини, збігу не може бути, бо рядок закінчується знаком `!`, але регулярний вираз в кінці очікує символ “слова” `pattern:\w` або пробіл `pattern:\s`. Але рушій цього не знає. Він перебирає всі комбінації "поглинання" рядку регулярним виразом `pattern:(\w+\s?)*`, включаючи варіанти з пробілами `pattern:(\w+\s)*` та без `pattern:(\w+)*` (бо пробіли `pattern:\s?` необов’язкові). Пошук є тривалим через велику кількість комбінацій (як на прикладі цифр). @@ -182,7 +182,7 @@ alert( regexp.test(str) ); Нажаль, це не допоможе: якщо замінити `pattern:\w+` на `pattern:\w+?`, регулярний вираз все одно висне. Зміниться порядок комбінацій, але не загальна кількість. -Деякі двигуни регулярних виразів мають хитро побудовані тести та скінченні автомати, що дозволяють оминути проходження всіма комбінаціями або пришвидшити, але це стосується меншості двигунів та не завжди допомагає. +Деякі рушії регулярних виразів мають хитро побудовані тести та скінченні автомати, що дозволяють оминути проходження всіма комбінаціями або пришвидшити, але це стосується меншості рушіїв та не завжди допомагає. ## Як це виправити? @@ -226,7 +226,7 @@ alert( regexp.test(str) ); // false На щастя, мається альтернативний підхід. Можна заборонити повернення для квантифікатора. -Корінь проблеми в двигуні регулярного виразу, що пробує багато очевидно невірних (для людини) комбінацій. +Корінь проблеми в рушії регулярного виразу, що пробує багато очевидно невірних (для людини) комбінацій. Наприклад, для людини очевидно, що в `pattern:(\d+)*$`, `pattern:+` не потребує пошуку з поверненням. Якщо замінити `pattern:\d+` на два окремих `pattern:\d+\d+`, нічого не зміниться: @@ -240,7 +240,7 @@ alert( regexp.test(str) ); // false Також, можливо, всередині початкового прикладу `pattern:^(\w+\s?)*$` ми б хотіли заборонити пошук з поверненням в `pattern:\w+`. Отже, `pattern:\w+` має знаходити збіг цілому слову, з максимально можливою довжиною. Нема потреби знижувати кількість повторень в `pattern:\w+`, ділити на два слова `pattern:\w+\w+`, і таке інше. -Для цього, сучасні двигуни регулярних виразів підтримують присвійні квантифікатори. Звичайні квантифікатори стають присвійними, якщо після них додати `pattern:+`. Саме так, ми використаємо `pattern:\d++` замість `pattern:\d+`, аби зупинити пошук з поверненням для `pattern:+`. +Для цього, сучасні рушії регулярних виразів підтримують присвійні квантифікатори. Звичайні квантифікатори стають присвійними, якщо після них додати `pattern:+`. Саме так, ми використаємо `pattern:\d++` замість `pattern:\d+`, аби зупинити пошук з поверненням для `pattern:+`. Насправді, присвійні квантифікатори є простішими за "звичайні". Вони просто шукають стільки збігів, скільки можуть, без будь-якого повернення. Такий процес пошуку, звичайно, простіший. @@ -261,7 +261,7 @@ alert( regexp.test(str) ); // false Розберемо його: - Перегляд вперед `pattern:?=` шукає найдовше слово `pattern:\w+`, починаючи з поточної позиції. -- Вміст дужок з `pattern:?=...` не запам’ятовується двигуном, тож огорнемо дужками `pattern:\w+`. В цьому випадку, двигун запам’ятає вміст дужок. +- Вміст дужок з `pattern:?=...` не запам’ятовується рушієм, тож огорнемо дужками `pattern:\w+`. В цьому випадку, рушій запам’ятає вміст дужок. - ...Це дозволяє нам посилатись на них всередині патерну: `pattern:\1`. Тобто, ми дивимось вперед – якщо там є слово `pattern:\w+`, то then match it as `pattern:\1`. From 4250f350b19f121323446bfd4348008918680519 Mon Sep 17 00:00:00 2001 From: Taras Date: Wed, 24 May 2023 21:50:28 +0300 Subject: [PATCH 03/23] Update 9-regular-expressions/15-regexp-catastrophic-backtracking/article.md --- .../15-regexp-catastrophic-backtracking/article.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md b/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md index 4845f2ffe..6d4176096 100644 --- a/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md +++ b/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md @@ -37,7 +37,7 @@ let str = "Введений рядок, який оброблятиметься alert( regexp.test(str) ); ``` -Чесно кажучи, відмітимо, що деякі рушії регулярних виразів можуть провести ефективно провести подібний пошук. Наприклад, версія рушію V8 починаючи з 8.8 здатна на це(тобто Google Chrome 88 не зависне), а Firefox все ж матиме проблеми. +Варто відмітити, що деякі рушії регулярних виразів можуть ефективно провести подібний пошук. Наприклад, версія рушія V8 починаючи з 8.8 здатна на це (тобто Google Chrome 88 не зависне), а Firefox все ж матиме проблеми. ## Спрощений приклад From f91db6ff5212f883077a46bed7d7f44327673b96 Mon Sep 17 00:00:00 2001 From: Taras Date: Wed, 24 May 2023 21:50:42 +0300 Subject: [PATCH 04/23] Update 9-regular-expressions/15-regexp-catastrophic-backtracking/article.md --- .../15-regexp-catastrophic-backtracking/article.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md b/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md index 6d4176096..85e17bc3d 100644 --- a/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md +++ b/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md @@ -31,7 +31,7 @@ alert( regexp.test("Погані символи: $@#") ); // false ```js run let regexp = /^(\w+\s?)*$/; -let str = "Введений рядок, який оброблятиметься довго або навіть спровокує зависання регулярного виразу!"; +let str = "Введений рядок, який довго оброблятиметься, або навіть спровокує зависання регулярного виразу!"; // займе дуже багато часу alert( regexp.test(str) ); From e236acfab0a2eae1a0ce081244b224824f5ff8e5 Mon Sep 17 00:00:00 2001 From: Taras Date: Wed, 24 May 2023 21:50:54 +0300 Subject: [PATCH 05/23] Update 9-regular-expressions/15-regexp-catastrophic-backtracking/article.md --- .../15-regexp-catastrophic-backtracking/article.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md b/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md index 85e17bc3d..2276415f4 100644 --- a/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md +++ b/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md @@ -1,6 +1,6 @@ # Катастрофічний пошук з поверненням -Деякі регулярні вирази виглядають простими, але можуть виконуватись дууууууже довгий час, та навіть змусити "зависнути" рушій JavaScript. +Деякі регулярні вирази виглядають простими, але можуть виконуватись дууууууже тривалий час, та навіть змусити "зависнути" рушій JavaScript. Рано чи пізно більшість розробників стикаються з подібною ситуацією. Типовий показник – часом регулярний вираз працює добре, але на конкретних рядках він "зависає", споживаючи 100% CPU. From d458e2c7ea11a681bac7563dc63d1200a6c1688e Mon Sep 17 00:00:00 2001 From: Taras Date: Wed, 24 May 2023 21:51:01 +0300 Subject: [PATCH 06/23] Update 9-regular-expressions/15-regexp-catastrophic-backtracking/article.md --- .../15-regexp-catastrophic-backtracking/article.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md b/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md index 2276415f4..985b85121 100644 --- a/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md +++ b/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md @@ -60,7 +60,7 @@ alert( regexp.test(str) ); По-перше, помітно, що регулярний вираз `pattern:(\d+)*` є трішки дивним. Квантифікатор `pattern:*` виглядає недоречним. Якщо нам потрібно число, можна використати `pattern:\d+`. -Дійсно, регулярний вираз is artificial, отриманий після спрощення попереднього прикладу. Але причина повільної роботи лишається тією ж. Зрозуміємо її джерело, тоді й попередній приклад стане очевидним. +Дійсно, регулярний вираз штучний; ми отримали його після спрощення попереднього прикладу. Але причина повільної роботи лишається тією ж. Тож розберемося в ній, і тоді попередній приклад стане очевидним. Що коїться під час пошуку `pattern:^(\d+)*$` в рядку `subject:123456789z` (трішки скорочений для ясності, відмітьте нецифровий символ `subject:z` наприкінці, він важливий), чому він триває так довго? From 72945010660810096db71ca0035b933a5c96d9ff Mon Sep 17 00:00:00 2001 From: Taras Date: Wed, 24 May 2023 21:52:24 +0300 Subject: [PATCH 07/23] Update 9-regular-expressions/15-regexp-catastrophic-backtracking/article.md --- .../15-regexp-catastrophic-backtracking/article.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md b/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md index 985b85121..53eaed9e3 100644 --- a/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md +++ b/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md @@ -62,7 +62,7 @@ alert( regexp.test(str) ); Дійсно, регулярний вираз штучний; ми отримали його після спрощення попереднього прикладу. Але причина повільної роботи лишається тією ж. Тож розберемося в ній, і тоді попередній приклад стане очевидним. -Що коїться під час пошуку `pattern:^(\d+)*$` в рядку `subject:123456789z` (трішки скорочений для ясності, відмітьте нецифровий символ `subject:z` наприкінці, він важливий), чому він триває так довго? +Що коїться під час пошуку `pattern:^(\d+)*$` в рядку `subject:123456789z`? Чому він займає так багато часу? Цей приклад трішки скорочений для ясності, зауважте нецифровий символ `subject:z` наприкінці – він важливий. Ось що робить рушій регулярних виразів: From 1dce423d306d6a17958f98b0d406eb71b8021a90 Mon Sep 17 00:00:00 2001 From: Taras Date: Wed, 24 May 2023 21:52:38 +0300 Subject: [PATCH 08/23] Update 9-regular-expressions/15-regexp-catastrophic-backtracking/article.md --- .../15-regexp-catastrophic-backtracking/article.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md b/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md index 53eaed9e3..29b6ba6b4 100644 --- a/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md +++ b/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md @@ -75,7 +75,7 @@ alert( regexp.test(str) ); Після поглинання всіх цифр, `pattern:\d+` вважається знайденим (як `match:123456789`). - Далі, застосовується квантифікатор `pattern:(\d+)*`. В тексті не залишилось цифр, тож зірка нічого не дає. + Далі, застосовується квантифікатор `pattern:(\d+)*`. В тексті не залишилось цифр, тож зірочка нічого не дає. Наступний символ патерну – кінець рядку `pattern:$`. Але в тексті маємо `subject:z`, тож збігу немає: From 0e3ea16c8b6d1689647e2015b86ab56c1679b22d Mon Sep 17 00:00:00 2001 From: Taras Date: Wed, 24 May 2023 21:52:50 +0300 Subject: [PATCH 09/23] Update 9-regular-expressions/15-regexp-catastrophic-backtracking/article.md --- .../15-regexp-catastrophic-backtracking/article.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md b/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md index 29b6ba6b4..754746036 100644 --- a/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md +++ b/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md @@ -77,7 +77,7 @@ alert( regexp.test(str) ); Далі, застосовується квантифікатор `pattern:(\d+)*`. В тексті не залишилось цифр, тож зірочка нічого не дає. - Наступний символ патерну – кінець рядку `pattern:$`. Але в тексті маємо `subject:z`, тож збігу немає: + Наступний символ шаблону – кінець рядка `pattern:$`. Але в тексті маємо `subject:z`, тож збігу немає: ``` X From f589c9e1a308308f15afee5dd73fe56ae061afd0 Mon Sep 17 00:00:00 2001 From: Taras Date: Wed, 24 May 2023 21:52:59 +0300 Subject: [PATCH 10/23] Update 9-regular-expressions/15-regexp-catastrophic-backtracking/article.md --- .../15-regexp-catastrophic-backtracking/article.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md b/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md index 754746036..2f5807006 100644 --- a/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md +++ b/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md @@ -268,7 +268,7 @@ alert( regexp.test(str) ); // false Чому? Все через те, що перегляд вперед знаходить слово `pattern:\w+` повністю та ми беремо його в патерн разом з `pattern:\1`. Отож, по суті, ми застосовуємо присвійний квантифікатор `pattern:+`. Він охоплює все слово `pattern:\w+`, а не якусь частину. -Для прикладу, в слові `subject:JavaScript` він не тільки знайде збіг `match:Java`, але й лишають `match:Script` для пошуку збігу з рештою патерну. +Для прикладу, в слові `subject:JavaScript` він не тільки знайде збіг `match:Java`, але й залише `match:Script` для пошуку збігу з рештою шаблону. Порівняння двох патернів: From 063354e6fbac7610a18b667fe1360948db0ae447 Mon Sep 17 00:00:00 2001 From: Taras Date: Wed, 24 May 2023 21:53:06 +0300 Subject: [PATCH 11/23] Update 9-regular-expressions/15-regexp-catastrophic-backtracking/article.md --- .../15-regexp-catastrophic-backtracking/article.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md b/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md index 2f5807006..79299f5ec 100644 --- a/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md +++ b/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md @@ -277,7 +277,7 @@ alert( "JavaScript".match(/\w+Script/)); // JavaScript alert( "JavaScript".match(/(?=(\w+))\1Script/)); // null ``` -1. В першому випадку, `pattern:\w+` спочатку бере ціле слово `subject:JavaScript`, але потім `pattern:+` символ за символом проводить пошук з поверненням, намагаючись знайти збіг для решти патерну доти, доки не досягне цілі (коли `pattern:\w+` відповідає `match:Java`). +1. В першому випадку, `pattern:\w+` спочатку бере ціле слово `subject:JavaScript`, але потім `pattern:+` символ за символом проводить пошук з поверненням, намагаючись знайти збіг для решти шаблону доти, доки не досягне цілі (коли `pattern:\w+` відповідає `match:Java`). 2. В другому випадку, `pattern:(?=(\w+))` дивиться вперед та знаходить слово `subject:JavaScript`, повністю включене в патерн за допомогою `pattern:\1`, тож опісля нема ніякої можливості для пошуку `subject:Script`. Ми можемо помістити більш комплексний регулярний вираз у `pattern:(?=(\w+))\1` замість `pattern:\w`, коли нам потрібно заборонити пошук з поверненням для `pattern:+` після нього. From 5cbce975e3b0693acdcd313a816df8089add174e Mon Sep 17 00:00:00 2001 From: Taras Date: Wed, 24 May 2023 21:53:26 +0300 Subject: [PATCH 12/23] Update 9-regular-expressions/15-regexp-catastrophic-backtracking/article.md --- .../15-regexp-catastrophic-backtracking/article.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md b/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md index 79299f5ec..f24bc5553 100644 --- a/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md +++ b/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md @@ -87,7 +87,7 @@ alert( regexp.test(str) ); 2. Без збігу, жадібний квантифікатор `pattern:+` зменшує кількість повторів та повертається на один символ назад. - Тепер `pattern:\d+` приймає усі цифри, окрім останньої(`match:12345678`): + Тепер `pattern:\d+` приймає усі цифри, окрім останньої (`match:12345678`): ``` \d+....... (12345678)9z From fcb6475682fb5e85371a13de79c821ce30948663 Mon Sep 17 00:00:00 2001 From: Taras Date: Wed, 24 May 2023 21:53:37 +0300 Subject: [PATCH 13/23] Update 9-regular-expressions/15-regexp-catastrophic-backtracking/article.md --- .../15-regexp-catastrophic-backtracking/article.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md b/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md index f24bc5553..7087cffe7 100644 --- a/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md +++ b/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md @@ -154,7 +154,7 @@ alert( regexp.test(str) ); - Для `123456789`, маємо `n=9`, що дає 511 комбінацій. - Довша послідовність `n=20` дасть приблизно мільйон (1048575) комбінацій. -- Для `n=30` - в тисячу разів більше (1073741823 комбінацій). +- Для `n=30` - в тисячу разів більше (1 073 741 823 комбінацій). Проходження кожною з них і є причиною повільного пошуку. From c0861b3e1aa82c03f026e9653b4e0db19a919c6f Mon Sep 17 00:00:00 2001 From: Taras Date: Wed, 24 May 2023 21:53:57 +0300 Subject: [PATCH 14/23] Update 9-regular-expressions/15-regexp-catastrophic-backtracking/article.md --- .../15-regexp-catastrophic-backtracking/article.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md b/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md index 7087cffe7..05f8e7c07 100644 --- a/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md +++ b/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md @@ -278,7 +278,7 @@ alert( "JavaScript".match(/(?=(\w+))\1Script/)); // null ``` 1. В першому випадку, `pattern:\w+` спочатку бере ціле слово `subject:JavaScript`, але потім `pattern:+` символ за символом проводить пошук з поверненням, намагаючись знайти збіг для решти шаблону доти, доки не досягне цілі (коли `pattern:\w+` відповідає `match:Java`). -2. В другому випадку, `pattern:(?=(\w+))` дивиться вперед та знаходить слово `subject:JavaScript`, повністю включене в патерн за допомогою `pattern:\1`, тож опісля нема ніякої можливості для пошуку `subject:Script`. +2. В другому випадку, `pattern:(?=(\w+))` дивиться вперед та знаходить слово `subject:JavaScript`, повністю включене в шаблон за допомогою `pattern:\1`, тож опісля нема ніякої можливості для пошуку `subject:Script`. Ми можемо помістити більш комплексний регулярний вираз у `pattern:(?=(\w+))\1` замість `pattern:\w`, коли нам потрібно заборонити пошук з поверненням для `pattern:+` після нього. From 849ad06c1509e4ea60ff12d22c1ff8c3e818cd0f Mon Sep 17 00:00:00 2001 From: Taras Date: Wed, 24 May 2023 21:54:05 +0300 Subject: [PATCH 15/23] Update 9-regular-expressions/15-regexp-catastrophic-backtracking/article.md --- .../15-regexp-catastrophic-backtracking/article.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md b/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md index 05f8e7c07..10de00bf0 100644 --- a/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md +++ b/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md @@ -298,7 +298,7 @@ let str = "Введений рядок, який оброблятиметься alert( regexp.test(str) ); // false, працює швидко! ``` -Як бачимо, `pattern:\2` використовується замість `pattern:\1` через додаткові зовнішні дужки. To avoid messing up with the numbers, ми можемо назвати дужки, наприклад, `pattern:(?\w+)`. +Як бачимо, `pattern:\2` використовується замість `pattern:\1` через додаткові зовнішні дужки. Щоб уникнути плутанини з числами, ми можемо назвати дужки, наприклад, `pattern:(?\w+)`. ```js run // дужки мають ім’я ?, на що посилається \k From 6678c6ef34698f9d0bddca7627cf42031eedf81d Mon Sep 17 00:00:00 2001 From: Taras Date: Wed, 24 May 2023 21:54:12 +0300 Subject: [PATCH 16/23] Update 9-regular-expressions/15-regexp-catastrophic-backtracking/article.md --- .../15-regexp-catastrophic-backtracking/article.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md b/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md index 10de00bf0..01c695b83 100644 --- a/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md +++ b/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md @@ -262,7 +262,7 @@ alert( regexp.test(str) ); // false - Перегляд вперед `pattern:?=` шукає найдовше слово `pattern:\w+`, починаючи з поточної позиції. - Вміст дужок з `pattern:?=...` не запам’ятовується рушієм, тож огорнемо дужками `pattern:\w+`. В цьому випадку, рушій запам’ятає вміст дужок. -- ...Це дозволяє нам посилатись на них всередині патерну: `pattern:\1`. +- ...Це дозволяє нам посилатись на них всередині шаблону: `pattern:\1`. Тобто, ми дивимось вперед – якщо там є слово `pattern:\w+`, то then match it as `pattern:\1`. From dec961a10ff1e37f9181e648027a590ecc6ebddc Mon Sep 17 00:00:00 2001 From: Taras Date: Wed, 24 May 2023 21:54:27 +0300 Subject: [PATCH 17/23] Update 9-regular-expressions/15-regexp-catastrophic-backtracking/article.md --- .../15-regexp-catastrophic-backtracking/article.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md b/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md index 01c695b83..eec39dea4 100644 --- a/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md +++ b/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md @@ -153,7 +153,7 @@ alert( regexp.test(str) ); Існує багато шляхів поділу послідовності цифр `123456789` на номери. Казати точно, усього 2n-1, де `n` - довжина послідовності. - Для `123456789`, маємо `n=9`, що дає 511 комбінацій. -- Довша послідовність `n=20` дасть приблизно мільйон (1048575) комбінацій. +- Довша послідовність `n=20` дасть приблизно мільйон (1 048 575) комбінацій. - Для `n=30` - в тисячу разів більше (1 073 741 823 комбінацій). Проходження кожною з них і є причиною повільного пошуку. From 53c33cb6a254a122f4caa9e4a8536da12e401a80 Mon Sep 17 00:00:00 2001 From: Taras Date: Wed, 24 May 2023 21:54:38 +0300 Subject: [PATCH 18/23] Update 9-regular-expressions/15-regexp-catastrophic-backtracking/article.md --- .../15-regexp-catastrophic-backtracking/article.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md b/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md index eec39dea4..2ba2ebcbd 100644 --- a/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md +++ b/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md @@ -160,7 +160,7 @@ alert( regexp.test(str) ); ## Повертаючись до слів та рядків -Подібне відбувається в нашому першому прикладі, коли ми шукаємо слова за патерном `pattern:^(\w+\s?)*$` в рядку `subject:Рядок, що висне!`. +Подібне відбувається в нашому першому прикладі, коли ми шукаємо слова за шаблоном `pattern:^(\w+\s?)*$` в рядку `subject:Рядок, що висне!`. Причина в тому, що слово може бути представлене як один чи кілька `pattern:\w+`: From 6d7da7f4af4011f23750d83662c68d6f74a467c1 Mon Sep 17 00:00:00 2001 From: Taras Date: Wed, 24 May 2023 21:54:53 +0300 Subject: [PATCH 19/23] Update 9-regular-expressions/15-regexp-catastrophic-backtracking/article.md --- .../15-regexp-catastrophic-backtracking/article.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md b/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md index 2ba2ebcbd..483ce7497 100644 --- a/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md +++ b/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md @@ -214,7 +214,7 @@ alert( regexp.test(str) ); // false (inp)(ut) ``` -Новий патерн інший: `pattern:(\w+\s)*` описує повторення слів, за якими йде пробіл! Рядок `subject:input` не буде збігом для двох повторень `pattern:\w+\s`, оскільки пробіл є обов’язковим. +Новий шаблон інший: `pattern:(\w+\s)*` описує повторення слів, за якими йде пробіл! Рядок `subject:input` не буде збігом для двох повторень `pattern:\w+\s`, оскільки пробіл є обов’язковим. Час проходження великою кількістю (взагалі-то, більшістю) комбінацій зекономлено. From a276ea1ccd6d264636561386ce9f34004a3cb819 Mon Sep 17 00:00:00 2001 From: Taras Date: Wed, 24 May 2023 21:55:06 +0300 Subject: [PATCH 20/23] Update 9-regular-expressions/15-regexp-catastrophic-backtracking/article.md --- .../15-regexp-catastrophic-backtracking/article.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md b/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md index 483ce7497..d877d22a1 100644 --- a/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md +++ b/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md @@ -254,7 +254,7 @@ alert( regexp.test(str) ); // false Тож, ми підібрались до дійсно передової теми. Нам потрібен квантифікатор, як-то `pattern:+`, без пошуку з поверненням, тому що іноді це не має ніякого сенсу. -Патерн з максимальною кількістю повторів `pattern:\w` без повернення: `pattern:(?=(\w+))\1`. Звісно, можемо взяти інший патерн замість `pattern:\w`. +Шаблон з максимальною кількістю повторів `pattern:\w` без повернення: `pattern:(?=(\w+))\1`. Звісно, можемо взяти інший шаблон, замість `pattern:\w`. Здається дивним, але це дуже просте перетворення. From 5cefb4d708eb5cd04fe71a7fc046dc9f6d657f4e Mon Sep 17 00:00:00 2001 From: Taras Date: Wed, 24 May 2023 21:55:18 +0300 Subject: [PATCH 21/23] Update 9-regular-expressions/15-regexp-catastrophic-backtracking/article.md --- .../15-regexp-catastrophic-backtracking/article.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md b/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md index d877d22a1..68fa674d4 100644 --- a/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md +++ b/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md @@ -264,7 +264,7 @@ alert( regexp.test(str) ); // false - Вміст дужок з `pattern:?=...` не запам’ятовується рушієм, тож огорнемо дужками `pattern:\w+`. В цьому випадку, рушій запам’ятає вміст дужок. - ...Це дозволяє нам посилатись на них всередині шаблону: `pattern:\1`. -Тобто, ми дивимось вперед – якщо там є слово `pattern:\w+`, то then match it as `pattern:\1`. +Тобто, ми дивимось вперед – якщо там є слово `pattern:\w+`, то воно відмітиться як `pattern:\1`. Чому? Все через те, що перегляд вперед знаходить слово `pattern:\w+` повністю та ми беремо його в патерн разом з `pattern:\1`. Отож, по суті, ми застосовуємо присвійний квантифікатор `pattern:+`. Він охоплює все слово `pattern:\w+`, а не якусь частину. From d9c6daba403f330a751bb0c6e88e66407aca2b4a Mon Sep 17 00:00:00 2001 From: Taras Date: Wed, 24 May 2023 21:55:31 +0300 Subject: [PATCH 22/23] Update 9-regular-expressions/15-regexp-catastrophic-backtracking/article.md --- .../15-regexp-catastrophic-backtracking/article.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md b/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md index 68fa674d4..8a1833132 100644 --- a/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md +++ b/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md @@ -266,7 +266,7 @@ alert( regexp.test(str) ); // false Тобто, ми дивимось вперед – якщо там є слово `pattern:\w+`, то воно відмітиться як `pattern:\1`. -Чому? Все через те, що перегляд вперед знаходить слово `pattern:\w+` повністю та ми беремо його в патерн разом з `pattern:\1`. Отож, по суті, ми застосовуємо присвійний квантифікатор `pattern:+`. Він охоплює все слово `pattern:\w+`, а не якусь частину. +Чому? Все через те, що перегляд вперед знаходить слово `pattern:\w+` повністю та ми беремо його в шаблон разом з `pattern:\1`. Отож, по суті, ми застосовуємо присвійний квантифікатор `pattern:+`. Він охоплює все слово `pattern:\w+`, а не якусь частину. Для прикладу, в слові `subject:JavaScript` він не тільки знайде збіг `match:Java`, але й залише `match:Script` для пошуку збігу з рештою шаблону. From 882643a5e6793b372b20675dc7d6c46e5ce5f8b9 Mon Sep 17 00:00:00 2001 From: Taras Date: Wed, 24 May 2023 21:55:44 +0300 Subject: [PATCH 23/23] Update 9-regular-expressions/15-regexp-catastrophic-backtracking/article.md --- .../15-regexp-catastrophic-backtracking/article.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md b/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md index 8a1833132..c099a5f99 100644 --- a/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md +++ b/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md @@ -270,7 +270,7 @@ alert( regexp.test(str) ); // false Для прикладу, в слові `subject:JavaScript` він не тільки знайде збіг `match:Java`, але й залише `match:Script` для пошуку збігу з рештою шаблону. -Порівняння двох патернів: +Порівняння двох шаблонів: ```js run alert( "JavaScript".match(/\w+Script/)); // JavaScript