Repeterende matches

In het vorige voorbeelden kwamen we wat vervelende zaken tegen. We waren alleen 3 of 4 letter woorden aan het matchen. Wanneer we woorden zouden willen matchen van verschillende grote in zenuwslopende alternatieven als \w\w\w\w|\w\w\w|\w\w\w

Dit is precies het probleem dat de quantifiers metcharacters ? * + en {} oplossen. Hierdoor kan je aangeven hoeveel keer een deel van een regexp moet matchen. Quantifiers kunnen achter een enkel teken, een character class of een group worden geplaatst.

Ze hebben de volgende betekenis:
a? = match 'a' 1 of 0 keer
a* = match 'a' 0 of meer keer
a+ = match 'a' 1 of meer keer
a{n,m} = match a op zijn minst n keer, maar niet meer dan m keer.
a{n,} = match a op zijn minst n keer
a{n} = match a precies n keer

Hier wat voorbeelden:

Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
/[a-z]+\s+\d*/

match een lowercase woord met zijn minst een spatie en meerdere nummers

Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
/(\w+)\s+\1/

Match twee gelijke woorden die na elkaar staan.

Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
/y(es)?/i;

Matched Y, y, yes en Yes.
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
<?
preg_match("/\d{2,4}/", $year);
//Het jaar moet uit 2 tot 4 cijfers bestaan
?>


Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
<?
preg_match("/\d{2}|\d{4}/", $year);
// Beter: 3 cijfer jaargetallen zijn niet toegestaan
?>

Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
<?
preg_match("/(\d{2}|\d{2})/", $year, $array);
// het zelfde in een andere notatie
// maar bij gebruik van $array is het met deze mogelijk om het
// gehele getal af te vangen in $array[0]

?>

Ook bij quantifiers geld dat php de eerste quantifier zoveel mogelijk keer zal laten proberen te matchen. Pas wanneer dit niet lukt zal naar alternatieve matches worden gezocht. Dus bij /a? ../ zal eerst gezocht worden naar een oplossing waar a in zit, daarna pas naar een oplossing zonder a.

Voor het * quantifier krijgen we het volgende resultaat:
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
<?
$x
= "the cat in the hat";
preg_match("/^(.*)(cat)(.*)$/", $x, $matches);
print_r($matches);
// [0] => 'the'
// [1] => 'cat'
// [2] => ' in the hat'

?>

Dit is omdat alleen cat in de string gevonden wordt, en maar een oplossing mogelijk is. Wanneer er meerdere oplossingen mogelijk zijn bijvoorbeeld bij 'at' krijg je andere resultaten:
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
<?
$x
= "the cat in the hat";
preg_match("/^(.*)(at)(.*)$/", $x, $matches);
print_r($matches);
// [0] => 'the cat in the h'
// [1] => 'at'
// [2] => ''

?>

Dit is omdat de eerste quantifier zoveel mogelijk keer gematched wordt, en zo doende wordt pas de laatste 'at gematched, en is de derde groep leeg. Als regel geld dat de meeste rechtse quantifier greedy is en zoveel mogelijk matched.

Wanneer een regexp op verschillende manieren kan matchen gebruiken we het bovenstaande principe om te voorspellen hoe de regexp zal matchen:

Prinicpe 0: Over het geheel gezien zal de regexp op de eerst mogelijke positie in de string matchen
Prinicpe 1: In een alternatie a|b|c zal de het eerste alternatief dat een resultaat opleverd worden gebruik
Principe 2: De maximal matching quantifiers ?, *, + en {n,m} zullen over het algemeen zoveel mogelijk keer matchen als mogelijk zolang het een resultaat opleverd voor de regexp
Principe 3: Als er twee of meer elementen in een regexp zijn, zal de meest linkse zoveel mogelijk keer matchen, zolang de regexp een resultaat heeft. Daarna zal de volgende linkse zo vaak mogelijk worden voldaan, enzovoorts.

Dit is de volgorde waarop een regexp zal matchen.

Hier is een voorbeeld van deze pincipes in actie:
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
<?
$x
= "The programming republic of Perl";
preg_match("/^(.+)(e|r)(.*)$/", $x, $matches);
print_r($matches);
// [0] => 'The programming republic of Pe'
// [1] => 'r'
// [2] => 'l'

?>

In dit geval begint de match gelijk al bij T. Bij de keuze uit e of r, matched r, omdat als eerste de linker greedy .* wordt voldaan.

Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
<?
$x
= "The programming republic of Perl";
preg_match("/^(m{1,2})(.*)$/", $x, $matches);
print_r($matches);
// [0] => 'mm'
// [1] => 'ing republic of Perl'

?>

Hier is de eerst mogelijke match bij de eerste 'm' van programming. m{1,2} is de eerste quantifier en worden het maximaal aantal matches (2) gekozen.
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
<?
$x
= "The programming republic of Perl";
preg_match("/^.*(m{1,2})(.*)$/", $x, $matches);
print_r($matches);
// [0] => 'm'
// [1] => 'ing republic of Perl'

?>


Hier matched de .* eerst zoveel mogelijk en is m{1,2} ook tevreden met 1, en matched de eerste groep slechts een m.
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
<?
$x
= "The programming republic of Perl";
preg_match("/^(.?)(m{1,2})(.*)$/", $x, $matches);
print_r($matches);
// [0] => 'a'
// [1] => 'mm'
// [2] => 'ing republic of Perl'

?>

Hier matched .? op de eerste positie mogelijk (de eerste positie die gevolgd wordt door een 'm') een letter, op de 'a'. Hierdoor worden de twee opvolgende 'mm' ook gematched.

Valkuilen:
Reguliere expressies kunnen je misleiden en onverwachte resultaten geven, daarom is het goed om inzicht te hebben hoe ze werken.
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
<?
preg_match("/(X*)/","aXXXb", $array);
// $array[0] = ''
?>

Hierbij levert X* bij de eerste mogelijkheid (de a) een geldige maximale hoeveelheid van 0 op. Omdat dat een goede match is (* is 0 of meer keer) wordt er niet verder gezocht naar andere matches.

In dit geval had X+ een betere optie geweest.

Soms is een afwijking van het greedy gedrag gewenst. Dat sommige quantifiers juist een minimale hoeveelheid van een string in plaats van zoveel mogelijk matchen. Voor dit doel zijn voor perl minimal match of anders gezegd non-greedy quantifiers gemaakt: ??, *?, +?, {}?. Dit zijn de gebruikelijke quantifiers met een ? eraan geplakt. Ze hebben de volgende betekenis:

- a?? = match 'a' 0 of 1 keer, probeer eerst 0 dan 1.
- a*? = match 'a' 0 of meer keer, in andere woorden: elk aantal, maar zo min mogelijk
- a+? = match 'a' 1 of meer keer, maar zo min mogelijk
- a{n,m}? = match 'a' op zijn minst n keer en niet meer dan m, maar zo min mogelijk
- a{n,}? = match 'a' op zijn minst n keer, en zo min mogelijk
- a{n}? = match 'a' precies n keer. Hier maakt de non-greedy quantifier geen verschil.


Laten we het vorige voorbeeld maar dan met minimal quantifiers bekijken:
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
<?
$x
= "The programming republic of Perl";
preg_match("/^(.+?)(e|r)(.*)$/", $x, $matches);
print_r($matches);
// [0] => 'Th'
// [1] => 'e'
// [2] => ' programming republic of Perl'

?>

Doordat de eerste groep Zo min mogelijk keer matched, zal de eerst mogelijke oplossing voor de tweede groep de 'e' zijn. De rest is vrij om in de derde groep te worden opgevangen.
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
<?
$x
= "The programming republic of Perl";
preg_match("/m{1,2}?(.*?)$/", $x, $matches);
print_r($matches);
// [0] => 'm'
// [1] => 'ming republic of Perl'

?>

Hier krijg je non-greedy gedrag op zijn top. Beide quantifiers willen zo min mogelijk, maar de eerste quantifier krijgt het minst. Doordat de tweede groep gelimiteerd is tot het eind van de string matched het niet 0 tekens, maar tot het eind van de string.
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
<?
$x
= "The programming republic of Perl";
preg_match("/(.*?)(m{1,2})?(.*?)$/", $x, $matches);
print_r($matches);
// [0] => 'The progra'
// [1] => 'm'
// [2] => 'ming republic of Perl'

?>

In deze regexp zou jemisschien verwachten dat de eerste minimal quantifier .*? een lege string zou matchen omdat het niet beperkt is door een anchor ^ om aan het begin te matchen. Het eerder genoemde principe 0 is hier van werking waar we het eerder over hadden: omdat het mogelijk is de string vanaf het begin te matchen, gebeurt dat ook. Dus is de eerste oplossing voor deze regexp van de eerste goep alle tekens tot de eerste m van programming.
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
<?
$x
= "The programming republic of Perl";
preg_match("/(.??)(m{1,2})(.*?)$/", $x, $matches);
print_r($matches);
// [0] => 'a'
// [1] => 'mm'
// [2] => 'ing republic of Perl'

?>

Net zoals in de vorige regexp is het voor de eerste quantifier .?? mogelijk om te matchen op de 'a' en gebeurt dat ook. De tweede quantifier is greedy en matched beide mm's. De derde matched de rest.

Het derde principe kunnen we herschrijven voor non-greedy quantifiers:
Principe 3: als er twe of meer elementen in een regexp zijn, zal de meest linker greedy (non-greedy) quantifier, als aanwezig, zoveel mogelijk (zo min mogelijk)

Edit:

Principe 3: Als er twee of meer elementen in een regexp zijn, zal de meest linkse greedy [non-greedy] zoveel mogelijk [zo min mogelijk] keer matchen, zolang de regexp een resultaat heeft. Daarna zal de volgende linkse zo vaak [min] mogelijk worden voldaan, enzovoorts.


Net als bij alternatie kunnen we het process stap voor stap doorlopen.

Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
<?
$x
= "the cat in the hat";
preg_match("/^(.*)(at)(.*)$/", $x, $matches);
// $matches[0] = 'the cat in the h'
// $matches[1] = 'at'
// $matches[2] = ''

?>

1. Start met de eerste letter in de string t
2. De eerste quantifier .* begint met het matchen van de hele string.
3. 'a' in het regexp element at matched niet aan het eind van de string, een teken terugspringen.
4. 'a' in de regexp matched nog steeds niet aan het einde van de string 't', dus doen we nog een stap terug
5. Nu kunnen we de 'a' en de 't' matchen.
6. We gaan door naar het derde element .* Omdat deze genoegen neemt met 0 keer matchen zijn we klaar en kunnen we naar huis.

Meestal zijn deze regexps behoorlijk snel. Sommige regexps zijn exponentieel langzamer, hoe groter de string wordt. Een typische structuur van een regexp die erg lang kan duren is
/(a|b+)*/

In dit geval zijn er vele verschillende mogelijkheden voor de regexp en worden ze in de eerder beschreven volgorde allemaal afgelopen. Het kan bij een lange string erg lang duren voordat er wel of geen match is gevonden.

Voor meer informatie is het boek Mastering regular expressions door Jeffrey Friedl aan te raden http://www.amazon.com/Mastering-Regular-Expressions-Jeffrey-Friedl/dp/0596528124

« Lees de omschrijving en reacties

Inhoudsopgave

  1. Voorwoord
  2. My First Regex
  3. Het gebruik van character classes
  4. Het een of het ander matchen
  5. Groeperen en hierarchisch matchen
  6. Het extraheren van matches
  7. Repeterende matches
  8. Disclaimer, bronvermelding, handige links, TODO

PHP tutorial opties

 
 

Om de gebruiksvriendelijkheid van onze website en diensten te optimaliseren maken wij gebruik van cookies. Deze cookies gebruiken wij voor functionaliteiten, analytische gegevens en marketing doeleinden. U vindt meer informatie in onze privacy statement.