Zoals ik hierboven heb uitgelegd, kun je groepen maken binnen een
reguliere expressie middels haakjes. De reguliere expressie
(\d{1,2})-(\d{1,2})-(\d{4})
kan bijvoorbeeld een datum beschrijven:
één of twee cijfers, gevolgd door een streepje, gevolgd door één of twee
cijfers, gevolgd door een streepje, gevolgd door vier cijfers (als je
deze reguliere expressie niet begrijpt, bestudeer dan de eerdere delen
van dit hoofdstuk totdat je hem wel begrijpt). Deze expressie bevat drie
groepen: de eerste bestaand uit één of twee cijfers, de tweede bestaand
uit één of twee cijfers, en de derde bestaand uit vier cijfers. De code
hieronder zoekt naar dit patroon in een string.
import re
pDatum = re.compile( r"(\d{1,2})-(\d{1,2})-(\d{4})" )
m = pDatum.search( "In antwoord op uw schrijven van 25-3-2015, \
heb ik besloten een huurmoordenaar op u af te sturen." )
if m:
print( "Datum: {}; dag: {}; maand: {}; jaar: {}".format(
m.group(0), m.group(1), m.group(2), m.group(3) ) )
Als je deze code uitvoert, zie je dat niet alleen het resultaat als
geheel wordt gevonden (via de methode group()
of group(0)
), maar ook
dat je ieder van de groepen afzonderlijk kunt benaderen, via methodes
group(1)
voor de dag, group(2)
voor de maand, en group(3)
voor het
jaar. Je kunt ook de methode groups()
gebruiken om een tuple te
krijgen waarin alle groepen zitten.
findall()
en groepenDe findall()
methode retourneert een list van patroon objecten. In de
voorbeelden die ik tot nu toe heb gegeven, was dat een list van strings.
En inderdaad is een patroon object een string als er ten hoogste één
groep in de reguliere expressie zit. Als er meerdere groepen zijn, is
een patroon object een tuple waarin alle groepen zitten.
import re
pDatum = re.compile( r"(\d{1,2})-(\d{1,2})-(\d{4})" )
datumlist = pDatum.findall( "In antwoord op uw schrijven van \
25-3-2015, heb ik op 27-3-2015 besloten u verder te negeren." )
for datum in datumlist:
print( datum )
Het is mogelijk om iedere groep een naam te geven, via de constructie
?P<naam>
(waarbij je voor “naam” de naam invult die je aan de groep
wilt geven – in dit geval laat je de <
en >
dus in de uitdrukking
staan) onmiddellijk achter het openingshaakje. Je kunt daarna aan de
groepen refereren met hun naam, in plaats van een index.
import re
pDatum = re.compile(
r"(?P<dag>\d{1,2})-(?P<maand>\d{1,2})-(?P<jaar>\d{4})" )
m = pDatum.search( "In antwoord op uw schrijven van 25-3-2015, \
heb ik besloten een zingend telegram op u af te sturen." )
if m:
print( "dag is {}".format( m.group('dag') ) )
print( "maand is {}".format( m.group('maand') ) )
print( "jaar is {}".format( m.group('jaar') ) )
Stel dat je een reguliere expressie moet schrijven die een string
representeert waarin een willekeurig teken (dat geen spatie is) twee
keer voorkomt. Bijvoorbeeld, de string “gasoven” zou geen match geven,
maar de string “magnetron” wel (aangezien de "n"
twee keer voorkomt).
Dit kun je niet doen met de mogelijkheden van reguliere expressies die
ik tot nu toe bediscussieerd heb. Je kunt het echter oplossen met
groepen, en speciale referenties binnen reguliere expressies, als volgt:
je gebruikt het speciale teken \x
, waarbij x
een cijfer is, waarmee
je dan refereert aan de groep met index x
in het patroon. Dus de
gevraagde reguliere expressie is (\S).*\1
.
Omdat op dit punt deze reguliere expressie misschien wat moeilijk te
begrijpen is, zal ik hem in detail doornemen. De \S
is een speciaal
teken dat een non-spatie voorstelt. Door er haakjes omheen te zetten,
wordt het een groep, en omdat de eerste (en enige) groep is, is de index
1. De .*
stelt een serie van nul of meer tekens voor die alles kunnen
zijn (de punt is een meta-teken dat ieder teken kan voorstellen).
Tenslotte is de \1
een referentie aan de eerste groep, en stelt dat
je hier exact moet hebben wat de eerste groep is. Als je je afvraagt of
je niet ook moet beschrijven wat er vóór de \S
of na de \1
komt,
dan is het antwoord dat dat niet nodig is, aangezien deze reguliere
expressie niet de string als geheel representeert. Dus zolang dit maar
ergens voorkomt in de string, wordt het patroon gevonden.
Test deze reguliere expressie in de code hieronder, waarbij je de string
"Monty Python's Flying Circus"
vervangt door andere strings, en het
resultaat van de uitvoering bekijkt.
import re
m = re.search( r"(\S).*\1", "Monty Python's Flying Circus" )
if m:
print( "{} komt twee keer voor in de string".format(
m.group(1) ) )
else:
print( "Geen match gevonden." )
Kun je de reguliere expressie in de code hierboven zo wijzigen dat het een patroon beschrijft waarbij een willekeurig teken minimaal drie keer voorkomt?
Kun je de reguliere expressie wijzigen zodat het een patroon beschrijft
waarbij twee tekens twee keer voorkomen? Dit is behoorlijk moeilijk en
daarom optioneel, maar als je het probeert, zorg er dan voor dat je het
test met op zijn minst de strings "aaaa"
, "aabb"
, "abab"
and
"abba"
. Deze moeten allemaal een match geven, tenzij je ook nog eist
dat de twee tekens verschillend moeten zijn, in welk geval "aaaa"
geen
match mag geven (maar dat maakt de reguliere expressie nog eens een stuk
moeilijker).