-
Idée générale
La notion de look-ahead apparaît en algorithmique pour traiter
les situations suivantes.
Lorsqu'on lit une chaîne (caractère par caractère), l'action standard
de lecture d'un caractère consiste à effectuer en même temps
deux opérations bien différentes :
- prendre connaissance du prochain caractère à lire
en pratique, affecter sa valeur à une variable du programme
- retirer ce caractère de la chaîne à lire.
Or, il arrive qu'on ait à faire un choix en fonction de la valeur lue,
et que ce choix s'accommode mal de la disparition du caractère
de la chaîne à lire : par exemple, si on veut arrêter la lecture...
L'exemple typique est "lire jusqu'à la prochaine occurrence d'un
caractère donné, exclue".
La lecture standard avec consommation du caractère lu oblige en ce cas
à gérer une variable supplémentaire contenant le caractère marquant la
fin, et qu'on a indûment consommé - il eût mieux valu le laisser en
place !
Pour cela il eût fallu être capable de
- regarder quel est le prochain caractère à lire
sans
pour autant l'ôter de la chaîne,
- et, dans un second temps, décider de consommer
ou
non
ce caractère sur la base de l'information ainsi acquise.
C'est cette capacité de regarder sans consommer que désigne
l'expression anglaise look-ahead (regarder en avant).
Dans la pratique des exp. reg. il arrive fréquemment qu'une partie de
l'expression serve à spécifier un motif marquant la fin de la zone
visée.
Si on effecture une substitution, il est alors nécessire de capturer ce
motif de manière à le restituer à l'identique.
Exemple : Dans
un texte ordinaire,
une majuscule n'apparaît qu'à l'initiale d'un mot, à moins que ce mot
ne soit tout entier en majuscules.
La présence d'une minuscule suivie d'une majuscule dénote donc une
faute de frappe qu'on peut souhaiter corriger :
$txt =~ s/(\p{Ll})(\p{Lu})/uc($1).$2/ge;
# 'uc
'
comme 'upperCase'
Dans l'exp. reg., la partie '(\p{Lu}
' filtre la majuscule
devant laquelle on doit trouver une autre majuscule, et qui doit être
laissée en place.
D'où la paire de parenthèses permettant de la retrouver comme valeur de
$2
.
On peut reformuler le processus en disant que toute minuscule doit
passer en majuscule lorsqu'on voit une majuscule qui la suit.
Je dis bien lorsqu'on voit (sans la consommer : look-ahead)
...
Dans ce qui suit, expression
désigne une expression
régulière quelconque.
-
(?=expression)
look-ahead
positif
For example, /\w+(?=\t)/
matches a word followed
by a tab, without including the tab
in $&
.
Mise en œuvre élémentaire : fichier look1.pl
my $er= "\\w+(?=\t)"; # ne pas oublier de doubler
le '\' !
my $chn = "il est
vraiment zozo";
my @res = $chn =~ m/$er/g;
foreach my $r (@res) {
print "$r\n";
}
jfp% perl look1.pl
il
est
vraiment
jfp%
Explication détaillée : comme la tabulation n'est pas directement
visible, prenons une chaîne de look-ahead plus manifeste
- mais moins utile en pratique !
D'abord, sans utiliser le look-ahead : fichier look2.pl
my $er= "\\w+tata"; #SANS look-ahead
my $chn = "il esttata
vraiment zozotata";
my @res = $chn =~ m/$er/g;
foreach my $r (@res) {
print "$r\n";
}
jfp% perl look2.pl
esttata
zozotata
jfp%
Et avec le look-ahead : fichier look3.pl
my $er= "\\w+(?=tata)"; #AVEC look-ahead
my $chn = "il esttata
vraiment zozotata";
my @res = $chn =~ m/$er/g;
foreach my $r (@res) {
print "$r\n";
}
jfp% perl look3.pl
est
zozo
jfp%
Vu ?
Revenons à notre exemple correctif
: on obtient le même résultat avec
$txt =~ s/(\p{Ll})(?=\p{Lu})/uc($1)/ge;
avec le double avantage que
- il y a une variable de moins
- et surtout, l'expression traduit exactement la pensée du
programmeur.
Autre exemple du même tonneau : dans un texte français, "coeur
"
et "oeil
" sont des fautes d'orthographe fréquentes pour "cœur
"
et "œil
".
$txt =~ s/oe(?=[ui])/\x{0153}/g;
# minuscule
$txt =~ s/O[eE](?=[uUiI])/\x{0152}/g;
#
majuscule
-
(?!expression)
look-ahead négatif (ne
pas être suivi de...)
For example /foo(?!bar)/
matches any occurrence of
``foo'
' that isn't followed by ``bar
''.
Exemple
: on reprend look3.pl
où l'exp. reg. devient : my
$er= "\\w+(?!tata)";
il
esttata
vraiment
zozotata
On note avec intérêt que les chaînes trouvées sont les
plus longues qui
- ne contiennent que des lettres ou chiffres (
\w
)
- ne soient pas suivies de "
tata
"
Cf. plus loin les questions de gourmandise.
-
(?<=expression
) look-behind positif
La notion de look-behind, forgée par symétrie sur look-ahead,
est spécifique aux exp. reg. de Perl.
Le look-behind est la partie de
l'expression qui sert à spécifier un motif marquant le début de la zone
visée.
Si on effecture une substitution, il est alors nécessire de capturer ce
motif de manière à le restituer à l'identique.
Exemple (suite) : Si dans un mot l'initiale et la
seconde occurrence sont des majuscules, alors le mot tout entier doit
être en majuscules.
$txt =~ s/(\p{Lu}\p{Lu})(\p{Ll}\p{L}*)/$1.uc($2)/ge;
La partie initiale '(\p{Lu}\p{Lu})
' ne sert qu'à marquer
le motif après lequel on doit passer en majuscules,
on ne le capture par la première paire de parenthèses que pour le
replacer illico dans la chaîne...
On écrira donc avec look-behind :
$txt =~ s/(?<=\p{Lu}{2})(\p{Ll}\p{L}*)/uc($1)/ge;
Contrairement à la précédente, cette notion ne joue aucun rôle en
algorithmique. En effet, du point de vue algorithmique,
la flèche du temps ne peut se renverser : avant n'est pas
symétrique de après.
Les exp. reg. énoncent des relations spatiales et non temporelles : devant
est symétrique de derrière.
Toutefois, dans les coulisses de l'implémentation, l'algorithmique
reprend ses droits !
La longueur de la chaîne look-behind doit être
connue à l'avance.
Une tentative avec
$txt =~ s/(?<=\p{Lu}+)(\p{Ll}\p{L}*)/uc($1)/ge;
soulève une protestation indignée :
Variable length lookbehind not implemented in regex
m/(?<=\p{Lu}+)(\p{Ll}\p{L}*)/ at newlook.pl line 26.
Autre exemple trouvé sur le réseau
For example, /(?<=\t)\w+/
matches
a word following a tab, without including the tab in $&
.
Works only for fixed-width lookbehind.
Variation
: idem avec pour exp. reg. : my $er=
"(?<=(?:est|zoz))\\w+";
noter la "parenthèse syntaxique "(?:
'', cf. cours n° 12 in fine,
sans quoi la construction est inopérante.
tata
otata
-
(?<!expression)
look-behind négatif
For example, /(?<!bar)foo/
matches any occurrence of
``foo'
' that isn't following ``bar
''.
Works only for fixed-width lookbehind.
Essayez vous-mêmes !
-
Autres possibilités
Notamment, la technique du look-around.
Voir la page Using Look-ahead and Look-behind où on trouve des indications précises comme celle-ci.
Note [however] that lookahead and
lookbehind
are not the same thing. You cannot use b. for lookbehind.
If you are
looking for a ``bar'
' that isn't preceded by a
``foo
'', /(?!foo)bar/
will not do what you
want.
That's because the (?!foo)
is just saying that the next
thing cannot be ``foo
''--and
it's
not, it's a ``bar'
', so ``foobar
'' will
match.
You would have to do
something like /(?!foo)...bar/
for that. We say ``like''
because
there's the case of your ``bar
'' not having three
characters before it.
You could cover that this way: /(?:(?!foo)...|^.{0,2})bar/
.
Sometimes
it's still easier just to say:
if (/bar/ && $` !~ /foo$/)