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 grep
pel, 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 boolean
t 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
Matcher
objektumot 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
Matcher
talá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
matches
re vagyunk kíváncsiak, akkor gyorsabb lehet-e aPattern
egyszeri 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 4000
stringből készítse el avan 1.001 kiskutyám és 1.234.567 hópihém, nem csak 4.000
stringet. - 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.
4a
azaaaa
string egy tömörítése,21b
pedig abbbbbbbbbbbbbbbbbbbbb
stringet kódolja el - hosszabb ismétlődő stringeket egy szám + nyitójel + string + csukójel kódol, így pl.
3(ab)
azababab
stringet,4(a)
azaaaa
stringet,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))
azabcaaaaaaaaaabbbcaaaaaaaaaabbbcaaaaaaaaaabbb
stringet kódolja. - a feladat egy ilyen módszerrel elkódolt string dekódolása, regex és capturing group használatával.