Greedy vs Lazy (*?, +?, ??) în Expresii Regulare (RegEx)

Cuantificatori (repetiții) PCRE / ECMAScript / Python

Controlează dacă un cuantificator consumă cât mai mult text posibil (greedy) sau cât mai puțin (lazy / non-greedy).

Tipar RegEx
.*?   |   .+?   |   ??   |   {n,m}?

Ce face Greedy vs Lazy (*?, +?, ??)

În mod implicit, toți cuantificatorii din RegEx (*, +, ?, {n,m}) sunt greedy (lacomi): ei încearcă să consume o porțiune cât mai lungă din șirul de intrare, dând înapoi (backtracking) doar atât cât este strict necesar pentru ca restul expresiei să se potrivească.

Adăugând un semn de întrebare (?) imediat după cuantificator (*?, +?, ??, {n,m}?), îl transformăm în lazy (leneș / non-greedy / reluctant): acesta va consuma numărul minim de caractere până la prima apariție a delimitatorului care urmează.

Variante și sintaxă asociată

Expresie / Simbol Descriere
* vs *? Greedy (*) consumă până la ULTIMUL delimitator; Lazy (*?) se oprește la PRIMUL.
+ vs +? Greedy (+) ia maximul de caractere (>=1); Lazy (+?) ia minimul (>=1).
{n,m}? Consumă inițial doar n caractere și mai adaugă doar dacă restul expresiei eșuează.
[^X]* Alternativă mai rapidă la .*? atunci când delimitatorul de închidere este un singur caracter X.
*+ / ++ Cuantificatori posesivi (doar în PCRE/PHP/Java): greedy fără backtracking.

Exemple

Exemplul 1: Extragerea conținutului din tag-uri HTML: .* (greedy) vs .*? (lazy)

Aceasta este cea mai frecventă capcană în RegEx: <b>.*</b> unește toate tag-urile <b> într-o singură potrivire uriașă.

Text:          "Oferta <b>specială</b> este valabilă <b>doar azi</b>!"
Greedy:        /<b>.*<\/b>/g
Lazy (corect): /<b>.*?<\/b>/g

Potriviri găsite (Matches)

Cu /<b>.*<\/b>/g (Greedy) -> 1 singură potrivire:
  Match 1: "<b>specială</b> este valabilă <b>doar azi</b>"

Cu /<b>.*?<\/b>/g (Lazy)  -> 2 potriviri corecte:
  Match 1: "<b>specială</b>"
  Match 2: "<b>doar azi</b>"

Exemplul 2: Extragerea comentariilor multi-linie /* … */ din cod

Cu flag-ul s (sau [\s\S]*?), cuantificatorul lazy se oprește la primul */ întâlnit.

Expresie: /\/\*[\s\S]*?\*\//g
Text:     "/* Comentariu 1 */ let x = 10; /* Comentariu 2 */"

Potriviri găsite (Matches)

Match 1: "/* Comentariu 1 */"
Match 2: "/* Comentariu 2 */"

Greșeli frecvente

  • Folosirea lui .*? la finalul unei expresii fără niciun delimitator după el (ex. /id=\d+.*?/): deoarece *? vrea să consume cât mai puțin, iar după el nu mai este cerut nimic, va consuma exact 0 caractere!
  • Ideea că .*? previne întotdeauna potrivirile peste mai multe blocuri: dacă expresia este de forma START.*?SpecificWord.*?END, primul .*? se va întinde totuși peste mai multe blocuri START...END care nu conțin SpecificWord.

Simboluri și rețete înrudite