
Regex greedy vs lazy: de ce șablonul tău înhață prea mult
Publicat 24 iul. 2026
Scrii <b>.*</b> ca să extragi conținutul unui tag bold, îl testezi pe <b>Hello</b>, merge perfect. Apoi îl rulezi pe HTML real, cu două taguri bold pe același rând — <b>Hello</b> and <b>World</b> — și în loc de două potriviri primești una uriașă, care se întinde peste ambele taguri. Nu e un bug al motorului tău regex; e comportamentul implicit al lui *, + și {n,m}, și are o soluție de un singur caracter odată ce înțelegi ce se întâmplă.
De ce .* înhață mai mult decât te aștepți
Implicit, cuantificatorii din regex sunt greedy (lacomi) — .* nu înseamnă „potrivește niște caractere“, ci „potrivește cât mai multe caractere posibil, apoi dă înapoi doar dacă restul șablonului o cere neapărat“.
Să parcurgem <b>.*</b> pe <b>Hello</b> and <b>World</b> pas cu pas:
<b>se potrivește cu primul<b>..*începe prin a consuma tot restul șirului — totul până la capăt, inclusiv al doilea<b>World</b>.- Motorul are apoi nevoie să găsească
</b>ca să încheie potrivirea, așa că începe să dea înapoi de la capătul lui.*, caracter cu caracter. - Primul loc (scanând înapoi de la capăt) unde
</b>se potrivește e chiar ultimul</b>din șir — nu primul, unde „logic“ ar trebui să se oprească.
Așa că potrivirea ajunge să fie <b>Hello</b> and <b>World</b> în întregime, pentru că potrivirea greedy încearcă mereu mai întâi cel mai lung șir posibil și îl micșorează doar atât cât e necesar ca restul șablonului să reușească.
Soluția: fă cuantificatorul lazy cu ?
Adăugarea unui ? imediat după un cuantificator îl comută din greedy în lazy (numit și „non-greedy“ sau „reluctant“): *?, +?, ??, {n,m}?.
Un cuantificator lazy face exact opusul: începe prin a potrivi cât mai puține caractere posibil, apoi se extinde doar dacă restul șablonului încă nu poate reuși.
<b>.*?</b> pe același șir:
<b>se potrivește cu primul<b>..*?începe prin a potrivi zero caractere.- Motorul verifică: se potrivește
</b>chiar aici? Nu (suntem la “Hello…”, nu încă la un</b>) — așa că extinde.*?cu exact un caracter și verifică din nou. - Asta se repetă caracter cu caracter până când
.*?a consumat exactHello, moment în care</b>se potrivește imediat.
Rezultat: <b>Hello</b> și <b>World</b> revin ca două potriviri separate, ceea ce e aproape întotdeauna ce voiai de fapt când parsezi structuri de tip tag sau delimitate.
Când chiar vrei greedy (nu e doar „implicitul greșit“)
Greedy nu e o greșeală a limbajului — e corect pentru un caz diferit, la fel de comun: potrivirea graniței exterioare a ceva, nu a celei mai mici unități din interior. Dacă extragi „tot ce e între primul { și ultimul }“ dintr-un text de tip JSON (să zicem, ca să prinzi un obiect întreg indiferent de imbricare), greedy e exact ce trebuie, iar lazy s-ar opri la primul } interior, dându-ți un fragment trunchiat, invalid.
Regula practică: lazy pentru bucăți mici delimitate, repetate (taguri, șiruri între ghilimele, elemente de listă); greedy pentru „prinde toată întinderea exterioară“.
Referință rapidă
| Șablon | Comportament | Folosește-l când |
|---|---|---|
.*, .+, {n,m} |
Greedy — potrivește cât mai lung posibil | Vrei întinderea exterioară sau există o singură potrivire în șir |
.*?, .+?, {n,m}? |
Lazy — potrivește cât mai scurt posibil | Ai mai multe bucăți similare delimitate și o vrei pe fiecare separat |
Dacă nu ești sigur ce face de fapt un șablon pe intrarea ta reală, nu pe șirul de test, rularea lui pe textul real cu potriviri multiple într-un regex tester cu evidențiere live a potrivirilor face diferența imediat vizibilă — vei vedea un singur bloc uriaș evidențiat pentru greedy versus mai multe blocuri separate pentru lazy, ceea ce e de obicei mai rapid decât să raționezi caracter cu caracter.