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` дасть приблизно мільйон (1 048 575) комбінацій.
+- Для `n=30` - в тисячу разів більше (1 073 741 823 комбінацій).
-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+`, то воно відмітиться як `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:(?Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: