CodeKitHub
Жадібний проти лінивого зіставлення в regex: чому ваш патерн захоплює забагато

Жадібний проти лінивого зіставлення в regex: чому ваш патерн захоплює забагато

Опубліковано 24 лип. 2026 р.

Ви пишете <b>.*</b>, щоб отримати вміст жирного тегу, перевіряєте на <b>Hello</b> — і все працює ідеально. Потім запускаєте це на реальному HTML із двома тегами жирного шрифту в одному рядку — <b>Hello</b> and <b>World</b> — і замість двох збігів отримуєте один величезний збіг, що охоплює обидва теги. Це не помилка у вашому regex-рушії; це стандартна поведінка *, + та {n,m}, і виправляється це одним символом, щойно ви розумієте, що відбувається.

Чому .* захоплює більше, ніж очікується

За замовчуванням квантифікатори в regex є жадібними.* означає не “збігтися з якоюсь кількістю символів”, а “збігтися з якомога більшою кількістю символів, і відступити назад лише тоді, коли решта патерну цього категорично вимагає”.

Пройдімося по кроках <b>.*</b> проти <b>Hello</b> and <b>World</b>:

  1. <b> збігається з першим <b>.
  2. .* починає з того, що поглинає весь інший рядок цілком — усе до самого кінця, включно з другим <b>World</b>.
  3. Далі рушію потрібно знайти </b>, щоб завершити збіг, тож він починає відступати від кінця .* по одному символу.
  4. Перше місце (при скануванні назад від кінця), де підходить </b>, — це останній </b> у рядку, а не той, на якому “логічно” мало б зупинитися.

Тож у підсумку збігом стає весь <b>Hello</b> and <b>World</b> цілком, бо жадібне зіставлення завжди спершу пробує найдовший можливий рядок і скорочує його лише настільки, наскільки необхідно, щоб решта патерну спрацювала.

Рішення: зробіть квантифікатор лінивим за допомогою ?

Додавання ? одразу після квантифікатора перемикає його з жадібного на лінивий (також званий “нежадібним” або “стриманим”): *?, +?, ??, {n,m}?.

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

<b>.*?</b> проти того самого рядка:

  1. <b> збігається з першим <b>.
  2. .*? починає зі збігу з нуль символів.
  3. Рушій перевіряє: чи збігається </b> саме тут? Ні (ми на “Hello…”, ще не дійшли до </b>) — тож він розширює .*? рівно на один символ і перевіряє знову.
  4. Це повторюється посимвольно, доки .*? не поглине рівно Hello, після чого </b> збігається одразу.

Результат: <b>Hello</b> та <b>World</b> повертаються як два окремі збіги, а це майже завжди те, чого ви насправді хотіли під час розбору структур на кшталт тегів чи роздільників.

Коли жадібність насправді потрібна (це не просто “неправильна поведінка за замовчуванням”)

Жадібність — це не помилка мови, вона правильна для іншого, так само поширеного випадку: зіставлення із зовнішньою межею чогось, а не з найменшою одиницею всередині нього. Якщо ви витягуєте “все між першою { і останньою }” у блоці тексту, схожого на JSON (наприклад, щоб захопити цілий об’єкт незалежно від вкладеності), жадібність буде саме тим, що треба, а лінивість натомість зупиниться на першій внутрішній }, давши вам обрізаний, недійсний фрагмент.

Практичне правило: лінивість — для повторюваних невеликих обмежених фрагментів (теги, рядки в лапках, елементи списку); жадібність — щоб “захопити весь зовнішній діапазон”.

Швидка довідка

Патерн Поведінка Використовуйте, коли
.*, .+, {n,m} Жадібний — збігається з якомога довшим фрагментом Вам потрібен найзовнішніший діапазон, або в рядку лише один збіг
.*?, .+?, {n,m}? Лінивий — збігається з якомога коротшим фрагментом У вас кілька схожих обмежених фрагментів, і кожен потрібен окремо

Якщо ви не впевнені, яку саме поведінку демонструє патерн на реальних вхідних даних, а не на тестовому рядку, запуск його на справжньому тексті з кількома збігами в тестері regex з підсвічуванням збігів у реальному часі одразу робить різницю очевидною — ви побачите один величезний підсвічений блок для жадібного варіанта і кілька окремих для лінивого, і це зазвичай швидше, ніж міркувати посимвольно.

← Повернутися до блогу