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 formaSTART.*?SpecificWord.*?END, primul.*?se va întinde totuși peste mai multe blocuriSTART...ENDcare nu conținSpecificWord.
Simboluri și rețete înrudite
+ și * (unu sau mai multe / zero sau mai multe): Specifică repetarea elementului precedent de cel puțin o dată (+) sau de zero ori mai multe ori (*).? (opțional: 0 sau 1): Marchează elementul precedent ca fiind opțional (poate apărea de 0 ori sau cel mult 1 dată).[^...] (set negat): Potrivește orice caracter care NU se află în lista sau intervalul dintre parantezele drepte.. (orice caracter): Potrivește orice caracter individual, cu excepția caracterului de linie nouă (newline).