
Жадібний проти лінивого зіставлення в 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>:
<b>збігається з першим<b>..*починає з того, що поглинає весь інший рядок цілком — усе до самого кінця, включно з другим<b>World</b>.- Далі рушію потрібно знайти
</b>, щоб завершити збіг, тож він починає відступати від кінця.*по одному символу. - Перше місце (при скануванні назад від кінця), де підходить
</b>, — це останній</b>у рядку, а не той, на якому “логічно” мало б зупинитися.
Тож у підсумку збігом стає весь <b>Hello</b> and <b>World</b> цілком, бо жадібне зіставлення завжди спершу пробує найдовший можливий рядок і скорочує його лише настільки, наскільки необхідно, щоб решта патерну спрацювала.
Рішення: зробіть квантифікатор лінивим за допомогою ?
Додавання ? одразу після квантифікатора перемикає його з жадібного на лінивий (також званий “нежадібним” або “стриманим”): *?, +?, ??, {n,m}?.
Лінивий квантифікатор робить протилежне: він починає зі збігу з якомога меншою кількістю символів, а розширюється лише тоді, коли решта патерну поки не може спрацювати.
<b>.*?</b> проти того самого рядка:
<b>збігається з першим<b>..*?починає зі збігу з нуль символів.- Рушій перевіряє: чи збігається
</b>саме тут? Ні (ми на “Hello…”, ще не дійшли до</b>) — тож він розширює.*?рівно на один символ і перевіряє знову. - Це повторюється посимвольно, доки
.*?не поглине рівноHello, після чого</b>збігається одразу.
Результат: <b>Hello</b> та <b>World</b> повертаються як два окремі збіги, а це майже завжди те, чого ви насправді хотіли під час розбору структур на кшталт тегів чи роздільників.
Коли жадібність насправді потрібна (це не просто “неправильна поведінка за замовчуванням”)
Жадібність — це не помилка мови, вона правильна для іншого, так само поширеного випадку: зіставлення із зовнішньою межею чогось, а не з найменшою одиницею всередині нього. Якщо ви витягуєте “все між першою { і останньою }” у блоці тексту, схожого на JSON (наприклад, щоб захопити цілий об’єкт незалежно від вкладеності), жадібність буде саме тим, що треба, а лінивість натомість зупиниться на першій внутрішній }, давши вам обрізаний, недійсний фрагмент.
Практичне правило: лінивість — для повторюваних невеликих обмежених фрагментів (теги, рядки в лапках, елементи списку); жадібність — щоб “захопити весь зовнішній діапазон”.
Швидка довідка
| Патерн | Поведінка | Використовуйте, коли |
|---|---|---|
.*, .+, {n,m} |
Жадібний — збігається з якомога довшим фрагментом | Вам потрібен найзовнішніший діапазон, або в рядку лише один збіг |
.*?, .+?, {n,m}? |
Лінивий — збігається з якомога коротшим фрагментом | У вас кілька схожих обмежених фрагментів, і кожен потрібен окремо |
Якщо ви не впевнені, яку саме поведінку демонструє патерн на реальних вхідних даних, а не на тестовому рядку, запуск його на справжньому тексті з кількома збігами в тестері regex з підсвічуванням збігів у реальному часі одразу робить різницю очевидною — ви побачите один величезний підсвічений блок для жадібного варіанта і кілька окремих для лінивого, і це зазвичай швидше, ніж міркувати посимвольно.