Regulaaravaldised
Nende küsimuste vastused tuleks kirjutada vastavasse muutujasse failis Exercises.java. Sellel on ka vastav test, millega saab ennast kontrollida.
Põhilised operaatorid
RE1: Valik ja rühmitused
Kasutame operaatorit |
, et leida kõik read, kus esinevad sõnad "kalad" või "jalad". Selleks võib muidugi kirjutada kalad|jalad
ja avaldise ühine tegur "alad" võib viia sulgude taha: (k|j)alad
. Valikud üksikute tähtede vahel võib kirjutada nurksulude vahele, näiteks (a|b|c|d|x|y|z)
asemel võib kirjutada [abcdxyz]
ja isegi [a-dx-z]
. Kasutades nurksulgudes loetletud tähtede rühmitusi, greppige kõik read, mis sisaldavad sõnad "kalad" või "jalad".
RE2: Punkt
Eriline tähtede rühmitus on .
, mis sisaldab kõik tähestiku tähte. Proovime nüüd sisendfailist välja greppida viietähelisi sõnu, mis lõppevad tähtedega "alad" ehk näiteks "jalad" ja "palad". Rohkem eeldefineeritud rühmitused on regex101 lehel all paremal nurgal reference → common tokens, eriti kasulikud on \w
, \d
ja \s
, aga ka \b
, mis sobitub sõnade piiridega.
RE3: Epsilon ja küsimärk
Kirjutame nüüd regulaaravaldise, et välja filtreerida read, mis sisaldavad sõna "color" nii inglise kui ka ameerika kirjaviisil. Selleks võib näiteks kirjutada colo(u|)r
. Praktikas kasutatakse ε asemel lihtsalt tühjust. See on aga niivõrd tüüpiline konstruktsioon, et selle jaoks on olemas sünonüüm: (E|)
asemel võib kirjutada E?
. Otsige nüüd üles read, mis sisaldavad sõnad "color" ja "colour", kasutades õiges kohas küsimärk.
RE4: Kordamisoperaator Kleene'i tärn
Kordamise operaator *
võimaldab leida üle suvaline arv tähekordusi, näiteks ja*
leiab üles kõik "jaa", "jaaa", ja "jaaaaa", aga kahjuks ka lihtsalt "j" ning "ja". Õigem oleks kirjutada jaaa*
(selle jaoks on ka olemas sünonüüm ja{2,}
). Kuna me tahame ühte või enamat kordust kaunis sagedasti, siis on selle jaoks eraldi tähis +
, nt. a+
on sama, mis aa*
. Proovige kirjutada regulaaravaldis, mis leiab üles kõik Rootsipärased "jaha!" moodi ütlused, kus a-de vahel sattub üks "h", näiteks "jahaa", "jaaaaha" ja "jaaaahaaaaaaaaaa".
Tehete prioriteet & süntaktiline suhkur
Mitme tehte koos kasutamisel tuleb teada tehete prioriteeti (või kasutada tehete grupeerimiseks sulge). Proovige katsetamise teel jõuda selgusele, kas avaldis b|a*
on samaväärne avaldisega (b|a)*
või avaldisega b|(a*)
.
Küllap on teil vaikselt tekkinud arusaam, et paljud lisakonstruktsioonid lisavad ainult mugavust, aga vabalt saaks ilma nendeta hakkama, näiteks on aa*
, a{1,}
ja a+
samaväärsed. Konstruktsioonid programmeerimiskeeltes, mis keele võimsust ei muuda, aga teevad kirjutamist mugavamaks, nimetatakse süntaktiliseks suhkruks. Te peaksite oskama näiteks (ab){2,4}
välja kirjutada ainult loengus defineeritud operaatorite abil.
Terve rida versus alamsõna matchimine
Teoorias huvitab meid ainult küsimus kas sõne kuulub regulaarde keelde või mitte, aga praktikas, kus me näiteks otsime alamsõne ja asendame seda millegi muuga, tekivad lisaküsimusi konteksti kohta. On olemas teatud spetsiaalsed sümbolid, mis sobituvad ridade ja sõnade algul olevate nähtamatute (tühjade) sümbolitega: ^
on nähtamatu sümbol rea algul, $
: nähtamatu sümbol rea lõpul ja \b
tähistab nähtamatud sümbolid sõnade algul ja lõpul. Paljudel vahenditel on lisaks ka \<
ja \>
sõnade alguse ja lõpu jaoks eraldi.
RE5: Binaarsõned
Proovime leida kõik read, mis ei sisalda muud kui ühtesid ja nullisid. Kui lihtsalt kirjutada [01]*
, siis saame kogu sisend tagasi, sest tühi sõne sisaldub igas reas. Siin peab kasutama ^
ja $
.
RE6: Eelviimane täht on a
Väljastage kõik read, kus on ainult 'a' ja 'b', aga mille eelviimane täht on "a".
Rühmad, tagasiviited, ja operatsioonid regexitega
Lõpuks vaatame ühte operaatorit, mis väga selgelt eristab praktikas kasutatavad regulaaravaldised meie formaalsest definitsioonist. Mõnikord kasutatakse sõna "regex", et eristada neid formaalse teooria regulaaravaldistest. Peamine selline operatsioon on tagasiviited.
RE7: Tagasiviited
Iga sulupaar moodustab nummerdatud rühm, millele võib tagasi viidata, näiteks (.)(.)\2\1
sobitub neljatäheliste palindroomidega nagu "ABBA". Proovige ennustada milliste sisenditega sobituvad ^(ab|ba)(ab|ba)$
ja (ab|ba)\1
. Oluline on siin aru saada, mille poolest need kaks avaldist teineteisest erinevad. Kirjutage tagasiviitega regex, mis sobitub neljatäheliste sõnadega, mis tükelduvad kaheks võrdeks osaks, näiteks "abab", "aaaa", "haha", jne.
RE8: Replace
Rühmi saab ka kasutada, et leitud sõne asendada teisega, näiteks saab teha järgmise teisenduse: "Eesnimi Perekonnanimi" -> "Perekonnanimi, Eesnimi". (Loengus oli näide vastupidisest suunast). Proovige Javas kirjutada seda teisendust. Proovige kõigepealt, kas oskate lihtsalt ära tunda Eesnimi Perekonnanimi? Asendamisel saab gruppidele viidata dollariga, näiteks $1
. Abiks võivad olla ka eeldefineeritud tähetüübid, vt. Pattern klassi doc. Võite ka proovida oma lemmik tekstiredaktoris teha ülalolev teisendus search&replace funktsionaalsuse abil. MS Word jaoks on juhend siin.
RE9: Lazy vs. greedy
Kui kasutatakse regulaaravaldised, et otsida ja asendada teksti, siis tekivad veel küsimusi, näiteks millega peaks "p.*p" sobituma sisendis "paapapaaaap"? Teoreetiliselt ei ole ju vahet, sest nii terve sõne, kui ka alamsõne "paaap" kuuluvad regulaaravaldise keelde. Teksti asendamisel on aga oluline aru saada "lazy versus greedy" kordamise operaatoritest. Proovige leida võimalikult lihtne regex, millega saab sõnes "paapapaaaap" nii "paap" kui ka talle järgnev "paaaap" asendada tähega "h", et tulemus okeks "hah" ja mitte lihtsalt "h".