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 õpiku esimesed 3 peatükki ja tutvuma 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/ ja 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.
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.
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:
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
Ü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.
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:
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
Kordused
Mustris saab veel ära määrata, et mõni sümbol või grupp saab olla sõnes mitu korda.
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:
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
Algus ja lõpp
Et täpsustada sõne algust ja lõppu, on olemas märgid ^
ja $
.
Sümbol | Tähendus |
---|---|
^ | Sõne algus. |
$ | Sõne lõpp. |
Näited:
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:
. [ ] \ | ( ) ? * + { } ^ $
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.
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 RegexOne. Proovi ka regulaaravaldiste ristsõnu või 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.
>>> import re
Mustreid defineeritakse justkui sõnesid, aga jutumärkide ette tuleb panna r-täht. See lubab sõnedes kasutada langkriipse ilma teise langkriipsuta.
>>> muster = r"\[\d+\]" >>> muster '\\[\\d+\\]'
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.
>>> 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") >>>
Tegelikult kontrollib re.match()
ainult, kas muster vastab sõne algusele. Et kontrollida tervet sõne, on mõistlik ette panna ^
ja taha $
.
>>> re.match(r"k..r", "koerad") <re.Match object; span=(0, 4), match='koer'> >>> re.match(r"^k..r$", "koerad")
Seda saab ära kasutada if-lausetes:
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.")
Millised kasutajanimed programmile meeldivad? Proovi erinevaid variante.
Sarnast kontrolli saab teha e-posti aadressidega, aga muster on keerulisem:
# https://emailregex.com/ email_regex = r"(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)"
Proovi muuta programmi nii, et kontrollitakse telefoninumbri korrektsust.
Mustrite otsimine sõnedes
Funktsioon re.findall()
leiab üles tekstist kõik mustri vastavused:
>>> tekst = "koer kass kaer koor koger k03r k??r" >>> muster = r"k..r" >>> re.findall(muster, tekst) ['koer', 'kaer', 'koor', 'k03r', 'k??r']
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:
>>> 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'
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.
>>> 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]"
Nende eemaldamine manuaalselt on tüütu ja neid koodiga eemaldada võib esmapilgul tunduda keeruline, aga regulaaravaldised teevad selle väga lihtsaks:
>>> 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.'
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
Enesekontrolliküsimused
Ü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 Riigikogu leheküljelt):
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
numbrileidja.py
käivitamine:
Sisesta failinimi: kontakt.txt Leitud telefoninumbrid: +372 631 6301 +3726316311 +372 6316321 6316302