CodeKitHub
Regex: pohlepno proti lenemu ujemanju — zakaj tvoj vzorec zajame preveč

Regex: pohlepno proti lenemu ujemanju — zakaj tvoj vzorec zajame preveč

Objavljeno 24. jul. 2026

Napišeš <b>.*</b>, da zajameš vsebino krepke oznake, jo preizkusiš na <b>Hello</b>, in deluje popolnoma. Nato jo poženeš na resničnem HTML z dvema krepkima oznakama v isti vrstici — <b>Hello</b> and <b>World</b> — in namesto dveh ujemanj dobiš eno velikansko ujemanje, ki zajame obe oznaki. To ni napaka v tvojem regex motorju; to je privzeto obnašanje *, + in {n,m}, popravek pa je dolg en sam znak, ko enkrat razumeš, kaj se dogaja.

Zakaj .* zajame več, kot pričakuješ

Privzeto so kvantifikatorji v regex pohlepni.* ne pomeni “ujemi nekaj znakov”, pomeni “ujemi čim več znakov, nato pa popusti le, če preostanek vzorca to nujno zahteva.”

Sledimo <b>.*</b> proti <b>Hello</b> and <b>World</b> korak za korakom:

  1. <b> se ujame s prvim <b>.
  2. .* začne s tem, da požre ves preostanek niza — vse do konca, vključno z drugim <b>World</b>.
  3. Motor mora nato najti </b>, da konča ujemanje, zato začne popuščati od konca .* po en znak naenkrat.
  4. Prvo mesto (gledano nazaj od konca), kamor se </b> prilega, je čisto zadnji </b> v nizu — ne prvi, pri katerem bi se “logično” moral ustaviti.

Tako ujemanje na koncu zajame <b>Hello</b> and <b>World</b> v celoti, ker pohlepno ujemanje vedno najprej poskusi najdaljši možni niz in ga skrči le toliko, kolikor je nujno potrebno, da preostanek vzorca uspe.

Popravek: naredi kvantifikator lenega z ?

Če takoj za kvantifikatorjem dodaš ?, ga preobrneš iz pohlepnega v lenega (tudi imenovanega “non-greedy” ali “reluctant”): *?, +?, ??, {n,m}?.

Len kvantifikator naredi nasprotno: začne z ujemanjem čim manj znakov, nato pa se razširi le, če preostanek vzorca še ne more uspeti.

<b>.*?</b> proti istemu nizu:

  1. <b> se ujame s prvim <b>.
  2. .*? začne z ujemanjem nič znakov.
  3. Motor preveri: se </b> ujame prav tukaj? Ne (smo pri “Hello…”, še ne pri </b>) — zato .*? razširi za natanko en znak in preveri znova.
  4. To se ponavlja znak za znakom, dokler .*? ne požre natanko Hello, na tej točki pa se </b> takoj ujame.

Rezultat: <b>Hello</b> in <b>World</b> se vrneta kot dve ločeni ujemanji, kar je skoraj vedno tisto, kar si dejansko želel pri razčlenjevanju struktur, podobnih oznakam ali ločilom.

Kdaj dejansko želiš pohlepno (to ni le “napačna privzeta izbira”)

Pohlepnost ni napaka v jeziku — pravilna je za drugačen, enako pogost primer: ujemanje z zunanjo mejo nečesa, ne z najmanjšo enoto znotraj njega. Če izluščuješ “vse med prvim { in zadnjim }” v bloku besedila, podobnega JSON (recimo, da zajameš cel objekt ne glede na gnezdenje), je pohlepnost natanko prava izbira, len kvantifikator pa bi se ustavil pri prvem notranjem }, s čimer bi dobil skrajšan, neveljaven fragment.

Praktično pravilo: len za ponavljajoče se majhne razmejene koščke (oznake, nizi v narekovajih, elementi seznama); pohlepen za “zajemi cel zunanji razpon.”

Hitri pregled

Vzorec Obnašanje Uporabi, ko
.*, .+, {n,m} Pohlepen — ujame najdaljše možno Želiš zunanji razpon, ali je v nizu le eno ujemanje
.*?, .+?, {n,m}? Len — ujame najkrajše možno Imaš več podobnih razmejenih koščkov in želiš vsakega posebej

Če nisi prepričan, kaj vzorec dejansko počne na tvojem resničnem vhodu namesto na testnem nizu, ga poženi proti dejanskemu besedilu z več ujemanji v preizkuševalniku regex z živim označevanjem ujemanj — takoj boš videl razliko: en velikanski označen blok za pohlepno proti nekaj ločenim za leno, kar je običajno hitreje kot razmišljanje znak za znakom.

← Nazaj na blog