Nu de basis van het gebruik van reguliere expressies in Python via de re module is uitgelegd, kan ik eindelijk beginnen met de uitleg van het echte schrijven van reguliere expressies.

Reguliere expressies met vierkante haken

De eenvoudigste reguliere expressie is een string van tekens, die een patroon beschrijft dat bestaat uit precies die string van tekens. Je mag ook een verzameling tekens beschrijven middels vierkante haken [ en ]. Bijvoorbeeld, de reguliere expressie [aeiou] beschrijft een teken dat een "a", "e", "i", "o", of "u" is. Dit betekent dat als [aeiou] onderdeel is van een reguliere expressie, dat op die locatie in het patroon één van die letters moet staan (en wel precies één, en niet meerdere). Bijvoorbeeld, om de woorden "ball", "bell", "bill", "boll" en "bull" te zoeken, kun je de reguliere expressie b[aeiou]ll gebruiken.

import re

slist = re.findall( r"b[aeiou]ll", "Bill Gates en Uwe Boll \
dronken Red Bull bij het voetballen in Campbell." )
print( slist )

Wijzig de reguliere expressie hierboven zodat niet alleen de woorden “ball” en “bell” gevonden worden, maar ook “Bill”, “Boll”, en “Bull”.

Je kunt tussen de vierkante haken een min-teken gebruiken tussen twee tekens om aan te geven dat niet alleen die twee tekens bedoeld worden, maar ook alle tekens die ertussen liggen. Bijvoorbeeld, de reguliere expressie [a-dqx-z] is equivalent met [abcdqxyz]. Om alle letters van het alfabet aan te geven, zowel als hoofd- of als kleine letter, kun je [A-Za-z] gebruiken.

Bovendien kun je een “dakje” (^) meteen rechts naast de vierkante-haak-open plaatsen om aan te geven dat je het tegengestelde bedoeld van wat er tussen de vierkante haken staat. Bijvoorbeeld, [^0-9] geeft aan alle tekens behalve cijfers.

Speciale tekens

In reguliere expressies, net als in strings, wordt het backslash teken (\) gebruikt om aan te geven dat het volgende teken een speciale betekenis heeft. De speciale tekens die je voor strings kunt gebruiken, gelden ook voor reguliere expressies, maar reguliere expressies hebben er meer. Er zijn ook een aantal meta-tekens die op een speciale manier geïntepreteerd worden. De volgende speciale tekens zijn gedefinieerd (er zijn er meer, maar deze worden het meest gebruikt):

symbool betekenis
\b woord begrenzing (breedte nul)
\B geen woord begrenzing (breedte nul)
\d cijfer [0-9]
\D geen cijfer [^0-9]
\n nieuwe regel (newline)
\r return
\s spatie (inclusief tabulatie)
\S geen spatie
\t tabulatie
\w alfanumeriek teken [A-Za-z0-9_]
\W geen alfanumeriek teken [^A-Za-z0-9_]
\/ voorwaartse slash
\\ backslash
\" dubbel aanhalingsteken
\' enkel aanhalingsteken
^ start van een string (breedte nul)
$ einde van een string (breedte nul)
. ieder teken

Merk op dat “breedte nul” betekent dat het teken niet een echt teken in de string aanduidt, maar een positie in de string tussen twee tekens (of het begin of einde van de string). Bijvoorbeeld, de reguliere expressie ^A representeert een string die start met de letter "A".

Bovendien kun je tekens of substrings tussen haakjes zetten, wat de tekens “groepeert.” Binnen een groep kun je een keuze tussen meerdere (groepjes) tekens aanduiden middels een pipe-line (|). Bijvoorbeeld, de reguliere expressie (appel|banaan|peer) is de string "appel" of de string "banaan" of de string "peer".

Je moet je ervan bewust zijn dat sommige van deze speciale tekens (zeker die zonder backslash, de haakjes, en de pipe-line) niet werken zoals aangegeven als ze tussen vierkante haken staan. Bijvoorbeeld, de punt tussen vierkante haken is niet “ieder teken,” maar een echte punt.

Herhaling

Waar reguliere expressie pas echt interessant worden is wanneer herhalingen worden gebruikt. Een aantal van de meta-tekens worden gebruikt om aan te geven dat (een deel van) een reguliere expressie meerdere keren herhaald wordt. De volgende herhalings-operatoren worden vaak gebruikt:

symbool betekenis
* nul of meer keer
+ 1 of meer keer
? nul of 1 keer
{p,q} minstens p en hoogstens q keer
{p,} minstens p keer
{p} precies p keer

Je zet zo’n operator achter het deel van de reguliere expressie dat herhaald moet worden. Bijvoorbeeld, ab*c betekent de letter "a", gevolgd door nul of meer keer de letter "b", gevolgd door de letter "c". Deze expressie representeert de strings "ac", "abc", "abbc", "abbbc", "abbbbc", etcetera.

Als je een herhalingsoperator zet achter een groepje (tussen haakjes), duidt het op de herhaling van het groepje. Bijvoorbeeld, (ab)*c representeert de strings "c", "abc", "ababc", "abababc", "ababababc", etcetera.

Het matchen van herhalingen gebeurt “gulzig” (Engels: “greedy”). Er wordt altijd geprobeerd het patroon zo vroeg mogelijk in de tekst te matchen, en de herhaling zo breed mogelijk uit te strekken. Bekijk de volgende code:

import re

mlist = re.finditer(r"ba+","Schaap zegt 'baaaaah' tot Ali Baba.")
for m in mlist:
    print( "{} is found at {}.".format(m.group(), m.start()))

Wijzig de reguliere expressie in de code hierboven zodat het iedere "b" die gevolgd wordt door één of meer "a"s vindt, waarbij de "b" eventueel een hoofdletter mag zijn. De uitvoer moet zijn "baaaaa", "Ba" en "ba".

Wanneer je de vorige opgave hebt opgelost, wijzig je de reguliere expressie zodat hij patronen vindt die bestaan uit een "b" of "B" gevolgd door één of meer "a"s, en dat één of meer keer herhaald. De uitvoer moet zijn "baaaaa" en "Baba". Je moet hier haakjes bij gebruiken. Test de reguliere expressie ook uit op een aantal andere teksten.

Hier is er nog een, die één of meer herhalingen van de letter "a" zoekt:

import re

mlist = re.finditer(r"a+","Schaap zegt 'baaaaah' tot Ali Baba.")
for m in mlist:
    print( "{} is found at {}.".format(m.group(), m.start()))

Als je deze code uitvoert, zie je dat het patroon vijf keer gevonden wordt: drie keer een enkele "a", een dubbele "a", en een groep van vijf "a"s. Je vraagt je misschien af waarom het proces niet ook de "a"s vindt binnen de langere patronen, bijvoorbeeld, de twee groepen van vier "a"s die te vinden zijn in de groep van vijf "a"s. De reden is dat de finditer() en findall() methodes, als ze een match gevonden hebben, verder gaan zoeken meteen na de laatst gevonden match. Gewoonlijk is dit het gedrag dat je wilt zien.

Wijzig nu de r"a+" in de code hierboven in r"a*", zodat gezocht wordt naar nul of meer "a"s. Voordat je de code uitvoert, probeer te bedenken wat het resultaat zal zijn. Voer dan de code uit en zie of je gelijk hebt. Indien niet, snap je dan wel waarom de uitkomst zo is als hij is?

Je zult wel gemerkt hebben dat reguliere expressies snel complex worden. Het is een goed idee om commentaar erbij te schrijven zodat je ook later je eigen code nog begrijpt.

Oefening

Met alles wat je tot nu toe geleerd hebt, moet je de volgende opgave kunnen maken. Het is verstandig om deze op te lossen voordat je met de rest van het hoofdstuk verder gaat. De opgave bestaat uit een stuk code dat je moet afmaken door een aantal reguliere expressies in te vullen.

Als je de code hieronder uitvoert, zoekt hij naar alle reguliere expressies in relist, in alle strings in slist. Dan wordt voor iedere string de nummers afgedrukt van de reguliere expressies waarvoor matches zijn gevonden. Je doel is om de reguliere expressies in relist in te vullen volgens de beschrijvingen die in het commentaar ernaast staan. Merk op dat de eerste zeven reguliere expressies de string als geheel beschrijven, en die moet je dus laten starten met een dakje en eindigen met een dollar teken, wat aangeeft dat de expressie de string van het begin (^) tot het einde ($) moet representeren.

import re

# List van strings die worden gebruikt voor testen.
slist = [ "aaabbb", "aaaaaa", "abbaba", "aaa", "bEver ottEr",
    "tango samba rumba", " hello world ", " Hello World " ]

# Reguliere expressies die moeten worden ingevuld.
relist = [
    r"",  # 1. Alleen a's gevolgd door alleen b's, inclusief ""
    r"",  # 2. Alleen a's, inclusief ""
    r"",  # 3. Alleen a's en b's, willekeurige volgorde, incl. "" 
    r"",  # 4. Precies drie a's
    r"",  # 5. Noch a's noch b's, maar "" is toegestaan
    r"",  # 6. Een even aantal a's (en niks anders)
    r"",  # 7. Precies twee woorden, ongeacht spaties
    r"",  # 8. Bevat een woord dat op "ba" eindigt
    r""   # 9. Bevat een woord dat begint met een hoofdletter
]

for s in slist:
    print( s, ':', sep='', end=' ' )
    for i in range( len( relist ) ):
        m = re.search( relist[i], s )
        if m:
            print( i+1, end=' ' )
    print()

De correcte uitvoer is:

aaabbb: 1 3   
aaaaaa: 1 2 3 6    
abbaba: 3 8    
aaa: 1 2 3 4     
bEver ottEr: 7    
tango samba rumba: 8     
 hello world : 5 7     
 Hello World : 5 7 9

Zorg dat je deze allemaal kunt oplossen voordat je verder gaat!