CodeKitHub
Regex greedy vs. lazy matching: proč váš vzor zabírá příliš mnoho

Regex greedy vs. lazy matching: proč váš vzor zabírá příliš mnoho

Publikováno 24. 7. 2026

Napíšete <b>.*</b>, abyste získali obsah tučného tagu, otestujete na <b>Hello</b>, funguje to perfektně. Pak to pustíte na skutečné HTML se dvěma tučnými tagy na jednom řádku — <b>Hello</b> and <b>World</b> — a místo dvou shod dostanete jednu obří shodu přes oba tagy. Není to chyba vašeho regex enginu; je to výchozí chování *, + a {n,m} a oprava je na jeden znak, jakmile víte, co se děje.

Proč .* zabírá víc, než čekáte

Kvantifikátory v regexu jsou ve výchozím stavu greedy (hladové) — .* neznamená „najdi nějaké znaky“, ale „najdi co nejvíc znaků a ustup jen tehdy, když to zbytek vzoru bezpodmínečně vyžaduje“.

Projděme si <b>.*</b> proti <b>Hello</b> and <b>World</b> krok za krokem:

  1. <b> odpovídá prvnímu <b>.
  2. .* začne tím, že spolkne celý zbytek řetězce — všechno až do konce, včetně druhého <b>World</b>.
  3. Engine pak potřebuje najít </b>, aby shodu dokončil, takže začne od konce .* couvat znak po znaku.
  4. První místo (při skenování pozpátku od konce), kam </b> pasuje, je úplně poslední </b> v řetězci — ne to první, u kterého by se „logicky“ mělo zastavit.

Výsledná shoda je tedy celé <b>Hello</b> and <b>World</b>, protože greedy matching vždy zkouší nejdřív nejdelší možný řetězec a zmenšuje ho jen o tolik, kolik je nezbytné, aby zbytek vzoru uspěl.

Oprava: udělejte z kvantifikátoru lazy pomocí ?

Přidání ? hned za kvantifikátor ho přepne z greedy na lazy (líný, také „non-greedy“ nebo „reluctant“): *?, +?, ??, {n,m}?.

Lazy kvantifikátor dělá opak: začne tím, že spolkne co nejméně znaků, a rozšiřuje se, jen když zbytek vzoru zatím nemůže uspět.

<b>.*?</b> proti stejnému řetězci:

  1. <b> odpovídá prvnímu <b>.
  2. .*? začne shodou s nula znaky.
  3. Engine se ptá: odpovídá </b> právě tady? Ne (jsme na „Hello…“, ještě ne u </b>) — takže rozšíří .*? o přesně jeden znak a zkontroluje znovu.
  4. Tohle se opakuje znak po znaku, dokud .*? nespolkne přesně Hello, a v tu chvíli </b> okamžitě odpovídá.

Výsledek: <b>Hello</b> a <b>World</b> se vrátí jako dvě samostatné shody, což je téměř vždy to, co jste při parsování tagovitých nebo oddělovačových struktur skutečně chtěli.

Kdy greedy skutečně chcete (není to jen „špatný výchozí stav“)

Greedy není chyba jazyka — je správný pro jiný, stejně běžný případ: hledání vnější hranice něčeho, ne nejmenší jednotky uvnitř. Pokud extrahujete „všechno mezi prvním { a posledním }“ v kusu JSON-like textu (třeba abyste vzali celý objekt bez ohledu na vnoření), je greedy přesně to pravé — lazy by se zastavil u první vnitřní } a dal vám oříznutý, neplatný fragment.

Praktické pravidlo: lazy pro opakované malé ohraničené kousky (tagy, řetězce v uvozovkách, položky seznamu); greedy pro „vezmi celý vnější úsek“.

Rychlý přehled

Vzor Chování Použijte, když
.*, .+, {n,m} Greedy — najde nejdelší možnou shodu Chcete vnější úsek, nebo je v řetězci jen jedna shoda
.*?, .+?, {n,m}? Lazy — najde nejkratší možnou shodu Máte víc podobných ohraničených kousků a chcete každý zvlášť

Pokud si nejste jistí, co vzor doopravdy dělá na vašem reálném vstupu (a ne na testovacím řetězci), spusťte ho proti skutečnému textu s více shodami v regex testeru s živým zvýrazňováním shod — rozdíl uvidíte okamžitě: jeden obří zvýrazněný blok pro greedy versus několik samostatných pro lazy, což bývá rychlejší než rozbor znak po znaku.

← Zpět na blog