!!3. Regulaaravaldised
Paljud programmeerijatel ette tulevad ülesanded on seotud sõnade ja teksti analüüsiga. Sellised ülesanded võivad osutuda üpris keeruliseks. Näiteks, kuidas teha kindlaks, et kasutaja sisestas ikka korrektse e-posti aadresi? Kuidas leida üles tekstist kõik telefoninumbrid? Kuidas[1] eemaldada[5] Vikipeedia[15] artiklist[43] kõik[119] viited[327]? Neid ülesandeid võib ju proovida puhta Pythoniga lahendama hakata, aga selliseid ja keerukamaidki ülesandied on palju lihtsam ja kiirem lahendada regulaaravaldistega.

!!!Ettevalmistus
Selle peatüki läbimiseks piisab Pythoni installatsioonist. 

Et peatükist aru saada, peab läbima [[https://progeopik.cs.ut.ee/osa1.html | õpiku]] esimesed 3 peatükki ja tutvuma [[https://progeopik.cs.ut.ee/07_listid.html#jarjendid | järjenditega]] 7. peatükist. 

!!!Regulaaravaldised
Regulaaravaldis on lihtsalt muster erinevatest sümbolitest, mille abil sõnede osasid otsida.  

Kõige lihtsam regulaaravaldise muster oleks mingi sõna ise. Näiteks mustriga "koer" saab teha kindlaks, kas sõne algab osasõnega "koer", leida sõnest kõik osasõne "koer" esinemised või asendada sõnes kõik osasõned "koer" millegi muuga. 

Selliseid ülesandeid on muidugi lihtne täita ka Pythonisse sisseehitatud funktsioonidega, vastavalt sõne meetodid @@startswith()@@, @@find()@@ ja @@replace()@@. Ülesanne muutub keeruliseks, kui otsitav muster ei ole lihtsalt mingi sõne. Näiteks, kuidas leiame kõik neljatähelised sõnad, mis algavad k-tähega ja lõppevad r-tähega?

Suvalisi sümboleid saab regulaaravaldistes märkida punktiga. Eelmises lõigus esitatud küsimuse vastuseks sobib muster "@@k..r@@". 

Regulaaravaldisi saab katsetada lehekülgedel [[https://regexr.com/ | https://regexr.com/]] ja [[https://regex101.com | https://regex101.com]]. Ülemisse tekstikasti kirjuta muster ja alumisse kirjuta tekste, millega mustrit katsetada. Mustrile vastavad osad värvitakse ära ning nende peale vajutades saab lisainfot. Katseta edaspidi kõik regulaaravaldiste oskused nende rakendustega läbi.

Attach:silmaring3_regexr.png

!!!!Erinevaid viise sümbolite tähistamiseks mustrites
Saime teada, et punktidega tähistatakse ükskõik mis sümboleid. Kui meile sobivad ainult mõned sümbolid, kasutame nurksulgi. Nende sisse lähevad sümbolid, mida otsime. 

|| border=1
||! Sümbol ||! Tähendus ||
||. ||Sobivad kõik sümbolid. ||
||[abc] ||Sobivad sümbolid a, b ja c. Nurksulgude vahele võib kirjutada mistahes sümboleid. ||
||[a-z] ||Sobivad tähed a kuni z ladina tähestikus. ||
||[A-Za-z0-9] ||Sobivad tähed A kuni Z, a kuni z ja numbrid 0-9. ||
||\w ||Sobivad kõik numbrid ja tähed (k.a täpitähed) ja alakriipsud. ||
||\d ||Kõik arvud. Sama, mis [0-9]. ||

Näited:

|| border=1
||! Muster ||! Sobivad ||! Ei sobi ||
||koer ||koer ||kõik muu ||
||k..r ||koer, kaer, koor, k03r, ... ||koger, kass, kor, kr, ... ||
||[ktpb]ass ||kass, tass, pass, bass ||kõik muu ||
||[0-9][0-9][0-9][A-Z][A-Z][A-Z] ||123ABC, 420BLZ, 111YKS jms tavalised auto numbrimärgid ||kõik, mis ei ole tavalised auto numbrimärgid ||
||\w\w\w\w\w\w ||kõik 6-tähemärgilised sõnad, mis sisaldavad tähti, numbreid ja alakriipse ||kõik muu ||

!!!!!Enesekontroll
(:includeurl https://courses.cs.ut.ee/LTAT.03.001/2020_fall/uploads/Main/silmaring3_1.html width=100% height="260px" border="0" :)
(:includeurl https://courses.cs.ut.ee/LTAT.03.001/2020_fall/uploads/Main/silmaring3_2.html width=100% height="230px" border="0" :)
(:includeurl https://courses.cs.ut.ee/LTAT.03.001/2020_fall/uploads/Main/silmaring3_3.html width=100% height="230px" border="0" :)

!!!!Üks või teine, grupeerimine
Erinevate tähtede võimalusi sai määrata nurksulgudega. Pikemaid võimalusi tuleb eraldada püstkriipsudega ja vajadusel paigutada sulgude sisse.

|| border=1
||! Sümbol ||! Tähendus ||
||| ||Või-märk. Sobib vasakule poole jääv grupp või paremale poole jääv grupp. ||
||() ||Grupeerimismärgid. Grupid tuleb ümbritseda sulgudega. ||

Näited:

|| border=1
||! Muster ||! Sobivad ||! Ei sobi ||
||koer|kass ||koer, kass ||kõik muu ||
||k(oer|ass) ||koer, kass ||kõik muu ||
||k(o|a)er ||koer, kaer ||kõik muu ||
||(ko|ka)(er|ss) ||koer, kass, kaer, koss ||kõik muu ||
||(je|bo|ka)ss ||jess, boss, kass ||kõik muu ||
||aias sadas (sai|leib)a ||aias sadas saia, aias sadas leiba ||kõik muu ||

!!!!!Enesekontroll
(:includeurl https://courses.cs.ut.ee/LTAT.03.001/2020_fall/uploads/Main/silmaring3_4.html width=100% height="230px" border="0" :)
(:includeurl https://courses.cs.ut.ee/LTAT.03.001/2020_fall/uploads/Main/silmaring3_5.html width=100% height="230px" border="0" :)

!!!!Kordused
Mustris saab veel ära määrata, et mõni sümbol või grupp saab olla sõnes mitu korda. 

|| border=1
||! Sümbol ||! Tähendus ||
||? ||Eelnev sümbol/grupp kas on või ei ole. ||
||* ||Eelnevat sümbolit/gruppi on null või rohkem kordi. ||
||+ ||Eelnevat sümbolit/gruppi on üks või rohkem kordi. ||
||{5} ||Eelnevat sümbolit/gruppi on täpselt 5 korda. ||
||{5,} ||Eelnevat sümbolit/gruppi on 5 või rohkem kordi. ||
||{1,5} ||Eelnevat sümbolit/gruppi on 1 kuni 5 korda (1 ja 5 kaasa arvatud). ||

Näited:

|| border=1
||! Muster ||! Sobivad ||! Ei sobi ||
||jaa? ||ja, jaa ||kõik muu ||
||(mitte )?olla ||olla, mitte olla ||kõik muu ||
||ja* ||j, ja, jaa, jaaa, ... ||kõik muu ||
||ja(ja)* ||ja, jaja, jajaja, ... ||kõik muu ||
||ja+ ||ja, jaa, jaaa, jaaaa, ... ||kõik muu ||
||ja{2,4} ||jaa, jaaa, jaaaa ||kõik muu ||
||[0-9]{3}[A-Z]{3} ||tavalised auto numbrimärgid ||mitte tavalised auto numbrimärgid ||
||\w{6} ||kõik 6-tähemärgilised sõnad, mis sisaldavad tähti, numbreid ja alakriipse ||kõik muu ||
||.+ ||kõik sõned, kus on vähemalt midagi ||tühisõne ||

!!!!!Enesekontroll
(:includeurl https://courses.cs.ut.ee/LTAT.03.001/2020_fall/uploads/Main/silmaring3_6.html width=100% height="230px" border="0" :)
(:includeurl https://courses.cs.ut.ee/LTAT.03.001/2020_fall/uploads/Main/silmaring3_7.html width=100% height="230px" border="0" :)

!!!!Algus ja lõpp
Et täpsustada sõne algust ja lõppu, on olemas märgid @@^@@ ja @@$@@. 

|| border=1
||! Sümbol ||! Tähendus ||
||^ ||Sõne algus. ||
||$ ||Sõne lõpp. ||

Näited:

|| border=1
||! Muster ||! Sobivad ||! Ei sobi ||
||^a ||aabits..., arvuti..., algebra..., a123..., a... ||muude sümbolitega algavad sõned ||
||ass$ ||...kass, ...bass, ...kontrabass, ... ||muude sümbolitega lõppevad sõned ||
||^koer$ ||koer ||kõik muu ||

!!!!Erisümbolid
Oleme vaadanud palju erisümboleid, millega regulaaravaldise mustreid koostada:
(:codestart gutter='false':)
. [ ] \ | ( ) ? * + { } ^ $
(:codeend:)

Aga kuidas neid sümboleid sõnedes otsida? Samamoodi, nagu Pythonis tehakse sõne sees jutumärke, saab ka regulaaravaldistes teha erisümboleid: nende ette tuleb panna langkriips. Inglise keeles öeldakse selle kohta ''escaping''. Lühidalt: et otsida punkti, peab otsima @@"\."@@, küsimärgi otsimiseks peab otsima @@"\?"@@, plussi otsimiseks @@"\+"@@ jne. 

|| border=1
||! Muster ||! Sobivad ||! Ei sobi ||
||\.\.\. ||... ||kõik muu ||
||youtube\.com ||youtube.com ||kõik muu ||
||\(.*\) ||(kass), (koer), (abc123), ... ||mitte sulgudes olevad sõned ||
||\[\d+\] ||[1], [42], [1632], ... ||[], mitte nurksulgude vahel olevad arvud ||
||¯\\_\(ツ\)_\/¯ ||¯\_(ツ)_/¯ ||kõik muu ||

!!!!Harjutamine
Regulaaravaldisi saab harjutada leheküljel [[https://regexone.com/ | RegexOne]]. Proovi ka regulaaravaldiste [[https://regexcrossword.com/ | ristsõnu]] või [[https://alf.nu/RegexGolf | golfi]].

!!!Regulaaravaldised Pythonis
Kasutame regulaaravaldisi lõpuks praktikas. Et Pythonis regulaaravaldisi kasutada, tuleb importida moodul @@re@@. Selle nimi on lühend ingliskeelsest terminist ''regular expressions''.

(:codestart python gutter='false':)
>>> import re
(:codeend:)

Mustreid defineeritakse justkui sõnesid, aga jutumärkide ette tuleb panna r-täht. See lubab sõnedes kasutada langkriipse ilma teise langkriipsuta.

(:codestart python gutter='false':)
>>> muster = r"\[\d+\]"
>>> muster
'\\[\\d+\\]'
(:codeend:)

!!!!Sõne vastavus mustriga
Uurime, kas muster @@"k..r"@@ vastab erinevatele sõnedele. Seda saab teha funktsiooni @@re.match()@@ abil. See funktsioon tagastab objekti, kui sõne vastab mustrile. Vastasel juhul ei tagasta see midagi. 

(:codestart python gutter='false':)
>>> muster = r"k..r"
>>> re.match(muster, "koer")
<re.Match object; span=(0, 4), match='koer'>
>>> re.match(muster, "kaer")
<re.Match object; span=(0, 4), match='kaer'>
>>> re.match(muster, "kass")
>>> 
(:codeend:)

Tegelikult kontrollib @@re.match()@@ ainult, kas muster vastab sõne algusele. Et kontrollida tervet sõne, on mõistlik ette panna @@^@@ ja taha @@$@@.

(:codestart python gutter='false':)
>>> re.match(r"k..r", "koerad")
<re.Match object; span=(0, 4), match='koer'>
>>> re.match(r"^k..r$", "koerad")
(:codeend:)

Seda saab ära kasutada if-lausetes:

(:codestart python gutter='false':)
import re

kasutajanimi = input("Sisesta kasutajanimi: ")
kasutajanimi_regex = r"^[a-z0-9_-]{3,20}$"

if re.match(kasutajanime_regex, kasutajanimi):
    print("See kasutajanimi sobib!")
else:
    print("See kasutajanimi ei sobi.")
(:codeend:)

Millised kasutajanimed programmile meeldivad? Proovi erinevaid variante.

Sarnast kontrolli saab teha e-posti aadressidega, aga muster on keerulisem:

(:codestart python gutter='false':)
# https://emailregex.com/
email_regex = r"(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)"
(:codeend:)

Proovi muuta programmi nii, et kontrollitakse telefoninumbri korrektsust. 

!!!!Mustrite otsimine sõnedes
Funktsioon @@re.findall()@@ leiab üles tekstist kõik mustri vastavused:

(:codestart python gutter='false':)
>>> tekst = "koer kass kaer koor koger k03r k??r"
>>> muster = r"k..r"
>>> re.findall(muster, tekst)
['koer', 'kaer', 'koor', 'k03r', 'k??r']
(:codeend:)

Proovi leida tekstist erinevaid mustreid: auto numbrimärgid, kasutajanimed, e-posti aadressid, telefoninumbrid. 

!!!!Mustrite asendamine sõnedes
Funktisooniga @@re.sub()@@ (sõnast ''substitute'') saab asendada tekstis kõik mustri vastavused:

(:codestart python gutter='false':)
>>> tekst = "koer kass kaer koor koger k03r k??r"
>>> muster = r"k..r"
>>> asendus = "kass"
>>> re.sub(muster, asendus, tekst)
'kass kass kass kass koger kass kass'
(:codeend:)

Selle koodiga asendasime kõik mustrile @@"k..r"@@ vastavad osasõned sõnega @@"kass"@@.

Kopeerisid Vikipeediast pika artikli, aga teksti sisse jäävad nurksulgudes viited. 

(:codestart python gutter='false':)
>>> tekst = "Python was conceived in the late 1980s[34] by Guido van Rossum at Centrum Wiskunde & Informatica (CWI) in the Netherlands as a successor to the ABC language (itself inspired by SETL),[35] capable of exception handling and interfacing with the Amoeba operating system.[8] Its implementation began in December 1989.[36]"
(:codeend:)

Nende eemaldamine manuaalselt on tüütu ja neid koodiga eemaldada võib esmapilgul tunduda keeruline, aga regulaaravaldised teevad selle väga lihtsaks:

(:codestart python gutter='false':)
>>> muster = r"\[\d+\]"
>>> re.sub(muster, "", tekst)
'Python was conceived in the late 1980s by Guido van Rossum at Centrum Wiskunde & Informatica (CWI) in the Netherlands as a successor to the ABC language (itself inspired by SETL), capable of exception handling and interfacing with the Amoeba operating system. Its implementation began in December 1989.'
(:codeend:)

!!!!Veel regulaaravaldisi?
Regulaaravaldistega on muidugi võimalik palju rohkem teha. Sellel kursusel rohkemat ei käsitleta. Regulaaravaldisi vaadatakse põhjalikumalt aines "Automaadid, keeled ja translaatorid" (LTAT.03.006).

Üks kasulik mainimata jäänud regulaaravaldise võimalus on gruppidest väärtuste saamine. 

Tasub ka uurida mooduli @@re@@ dokumentatsiooni: [[https://docs.python.org/3/library/re.html | https://docs.python.org/3/library/re.html]] 

!!!Enesekontrolliküsimused
(:includeurl https://courses.cs.ut.ee/LTAT.03.001/2020_fall/uploads/Main/silmaring3_8.html width=100% height="230px" border="0" :)
(:includeurl https://courses.cs.ut.ee/LTAT.03.001/2020_fall/uploads/Main/silmaring3_9.html width=100% height="260px" border="0" :)
(:includeurl https://courses.cs.ut.ee/LTAT.03.001/2020_fall/uploads/Main/silmaring3_10.html width=100% height="230px" border="0" :)

!!!Ülesanded
1. Kirjuta programm @@numbrileidja.py@@, mis leiab failist üles kõik telefoninumbrid järgmise vorminguga:
* Alguses on +372, aga mitte alati.
* Järgmisena võib tulla tühik.
* Järgmisena tuleb 3 või 4 numbrit.
* Järgmisena võib tulla tühik.
* Järgmisena tuleb 4 numbrit.

Mõne telefoninumbri näited:
* @@+372 1234 5678@@
* @@1234 5678@@
* @@123 4567@@
* @@+37212345678@@
* @@+3721234567@@
* @@+372 12345678@@
* @@+372123 4567@@

Näiteks @@kontakt.txt@@ sisu (andmed pärit [[https://www.riigikogu.ee/riigikogu/riigikogu-kantselei/kontaktid/ | Riigikogu leheküljelt]]):

(:codestart gutter='false':)
Riigikogu
Henn Põlluaas +372 631 6301 henn.polluaas@riigikogu.ee
Helir-Valdor Seeder +3726316311 helir-valdor.seeder@riigikogu.ee
Siim Kallas +372 6316321 siim.kallas@riigikogu.ee
Tiiu Pohl 6316302 tiiu.pohl@riigikogu.ee
(:codeend:)

@@numbrileidja.py@@ käivitamine:
(:codestart gutter='false':)
Sisesta failinimi: kontakt.txt
Leitud telefoninumbrid:
+372 631 6301
+3726316311
+372 6316321
6316302
(:codeend:)
