CodeKitHub
Regex greedy vs lazy: de ce șablonul tău înhață prea mult

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:

  1. <b> se potrivește cu primul <b>.
  2. .* începe prin a consuma tot restul șirului — totul până la capăt, inclusiv al doilea <b>World</b>.
  3. 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.
  4. 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:

  1. <b> se potrivește cu primul <b>.
  2. .*? începe prin a potrivi zero caractere.
  3. 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.
  4. Asta se repetă caracter cu caracter până când .*? a consumat exact Hello, 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.

← Înapoi la blog