3. gyakorlat¶
Non-capturing groupok¶
Mivel a capture tárolása is memóriát igényel illesztés közben, egyfajta extra bookmarkingot, továbbá a gond, hogy ha zárójeles regexet
kell másoljunk egy meglévőben refaktoráláskor, elronthatja a csoportok indexelését, végül azért, mert sok nyelv csak legfeljebb tíz
csoport capture-ülését támogatja, felmerül az igény, hogy legyen olyan zárójelünk is, ami tényleg csak a műveleti precedenciáért felel,
és nem capture-öl. Ezek a non-capturing groupok és a jelük rendszerint a (?: nyitójel és ) csukójel, így Javában is.
Így például a ^(?:blue|red)*(green(?:blue|red|green)*)*$ regex egy olyan stringre illeszkedik, melyet a red, blue, green szavak
valamilyen sorrendben, bármelyik bárhányszor, alkotnak, pl. redredgreenredgreenblue, illeszkedés esetén ez az eredeti string kerül a nulladik,
és az első green, valamint az azt követő rész kerül majd az egyes csoportba, mert az első green előtti rész non-capturing groupban van.
Továbbá szintén non-capturing groupban van a green utáni rész - az iterálton belüli group egyébként is csak egyet kapna el az illeszkedő
substringekből, mégpedig a legutolsót.
Java regex alapok¶
Javában a String osztály boolean matches(String regex) függvényével végezhetünk illesztést egy regexre, pl.
"alma".matches(".*m.*") igaz lesz, "alma".matches("a") pedig hamis (ellentétben a greppel, az itteni match függvény
a teljes inputra próbálja illeszteni a regexet).
Ha ennél összetettebb feladatot szeretnénk elvégezni (mint pl. capturing groupokkal dolgozni), akkor a
java.util.regex.Pattern és a java.util.regex.Matcher osztályok lesznek a barátaink, erre példa:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | |
A Java java.util.regex package¶
A lentebbi kódok egyben itt:
match¶
A Matcher objektumunk matches() függvénye egy boolt ad vissza, igaz, ha a string, mellyel paraméterezve
a matchert létrehoztuk, illeszkedik a reguláris kifejezésre, akinek a matcher függvényével őt létrehoztuk.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |
groups¶
A matches() függvény hívása után a Matcher objektumtól, ha volt illeszkedés, el tudjuk kérni az elkapott csoportok
tartalmát: groupCount() mondja meg a csoportok számát, group(i) pedig az i-edik csoport tartalmát (ami egy string
lesz, 0 <= i <= groupCount(), itt is a 0. csoport a teljes illeszkedő string.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | |
search¶
Ha nem az egész stringre akarjuk illeszteni a regexet, hanem keresünk benne illeszkedő substringet,
az a Matcher objektumunk find() metódusával megoldható, szintén booleant ad vissza, és találat
esetén feltölti a csoportokat:
1 2 3 4 5 6 7 8 9 10 11 12 | |
Ha mindent meg akarunk keresni, ahhoz a Matcher következő metódusait használhatjuk:
- A
Matcherobjektumot létrehozásakor a teljes input texthez kötöttük. Afind( int from )metódusának meg tudjuk adni, hogy hanyadik pozíciótól kezdve keressen találatot. - Ha pedig a
Matchertalált egy matchet, akkor azend()metódusával kaphatjuk meg azt a pozíciót, ami a vége (és már nincs benne a matchben).
Ezt a kettőt összekombinálva találhatunk egy grep-like searchAll-t:
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
replace¶
Flagek helyett Javában a Matcher osztálynak van külön egy replaceAll és egy replaceFirst metódusa,
mindkettő megkapja argumentumként, hogy mire cserélje ki a találato(ka)t:
1 2 3 4 5 6 7 8 | |
1 2 3 4 5 6 7 8 | |
A replacement stringben használhatóak a $0, $1 stb. meták is, melyek helyére
a megfelelő captured groupok kerülnek be (note: míg a regexen belül itt is \k
jelzi a k. captured csoportot, replace-ben $ a prefix!):
1 2 3 4 5 6 7 8 9 10 | |
0 és 9 közti indexeket parsol fel, tehát maximum kilenc captured grouppal tudunk dolgozni!
Lookahead¶
Van, amikor jól jön, ha egy-egy pozícióján a stringnek szeretnénk tesztelni, hogy a pozíció utáni rész(nek egy prefixe)
illik-e egy mintára, és ha illik, akkor nem feldolgozni azt a részt, hanem onnan folytatni az illesztést, ahol
tartottunk, mintha csak "előrenéznénk" az inputot feldolgozás nélkül. Ezt lookaheadnek hívják és a jele
Javában (és C++ban is) a (?= nyitó- és ) zárójel. Tehát amikor egy (?=R)T regexet illesztünk
egy pozíción, akkor
- először a motor megnézi, hogy ezen a pozíción kezdődik-e olyan substringje a teljes inputnak, ami illeszkedik
R-re; - ha nem, akkor itt nincs illeszkedés, ha viszont igen, akkor megpróbálja ugyanerről a pozícióról illeszteni
T-t (az már fel fogja dolgozni a karaktereket, ahogy szokta is, amikre illeszkedik).
Hívják a lookaround műveleteket non-consuming csoportnak is, mert nem használja fel az inputból a karaktereket, melyekre illeszkedik.
(note: a grep/sed nem támogatja a lookaheadet -- amennyire hinni lehet az internetnek, a körbenéző operátorokat, mint a lookahead is, csak a PCRE backtracking motorok támogatják)
Van még:
- negatív lookahead: egy pozíción akkor illeszkedik a
(?!R)regex, ha a pozíció utáni stringnek nincs olyan prefixe, melyre illeszkedikR. - (pozitív) lookbehind: egy pozíción akkor illeszkedik a
(?<=R)regex, ha a pozíció előtti stringnek van olyan suffixe, amire illeszkedikR. - negatív lookbehind: egy pozíción akkor illeszkedik a
(?<!R)regex, ha a pozíció előtti stringnek nincs olyan suffixe, amire illeszkedikR.
Messze nem minden motor támogatja ezeket a lookaround műveleteket: a std::regex pl. nem támogatja a lookbehindot.
A Java motor is csak akkor, ha tud kiszámolni egy előre ismert felső korlátot a lookbehind hosszára, így pl. csillagot
nem írhatunk lookbehind kifejezés belsejébe (de pl. egymilliószor való ismétlést igen).
A negatív lookahead hasznos lehet, ha pl. olyan feltételünk van, ami azt mondja, hogy a mintánkat nem követi pl. valamilyen
karakter: a q[^u] mintára findolva megkapjuk az összes olyan q betűt az input stringben, ami után jön egy karakter, és
az nem u, de egyrészt ez lenyeli ezt a második karaktert is (tehát pl. ha qqx van a stringünkben, akkor csak az első
q-ra jelez találatot), másrészt a szó végén lévő q-ra nem illik (mert a [^u]-nak kell egy karakter, ami nem u,
de akkor is kell neki egy karakter). Ehelyett a q(?!u) regexet illesztve minden olyan q karaktert megtalálunk a stringben,
ami után nem jön u betű, akár a string végén van, akár nem.
Feladatok¶
- Javában mérjük ki egy komplikáltabb reguláris kifejezés sok rövid sorra való illesztésével, hogy ha csak
matchesre vagyunk kíváncsiak, akkor gyorsabb lehet-e aPatternegyszeri létrehozása, majd illesztése aString.matches(String regex)függvény sorozatos hívásánál. - Írjunk függvényt C++-ban vagy Javában, mely az inputként kapott stringből készít egy olyan stringet, ami az eredeti
string minden számjában hármasával pontokat helyez el a szokásos módon! Pl. a
van 1001 kiskutyám és 1234567 hópihém, nem csak 4000stringből készítse el avan 1.001 kiskutyám és 1.234.567 hópihém, nem csak 4.000stringet. - Oldjuk meg kedvenc programozási nyelvünkön a következő dekódoló feladatot (from ggmarkk prog1 stepik):
- egy, csak kis- és nagybetűket (szorítkozhatunk mondjuk az angol ábécé kis- és nagybetűire) tartalmazó string tömörített változatát kapjuk
- a tömörítés lényege, hogy az ismétlődő stringeket ismétlésszám + string alakba konvertálja valamilyen módon rekurzívan
- ha egy karakter ismétlődik valahányszor, az csak egy szám + egy karakter egymás után írva, így pl.
4aazaaaastring egy tömörítése,21bpedig abbbbbbbbbbbbbbbbbbbbbstringet kódolja el - hosszabb ismétlődő stringeket egy szám + nyitójel + string + csukójel kódol, így pl.
3(ab)azabababstringet,4(a)azaaaastringet,0(abc)és65()pedig az üres stringet kódolják - lehetnek egymásba ágyazva is a tömörítő jelzetek, így pl.
ab3(c10a3(b))azabcaaaaaaaaaabbbcaaaaaaaaaabbbcaaaaaaaaaabbbstringet kódolja. - a feladat egy ilyen módszerrel elkódolt string dekódolása, regex és capturing group használatával.