Nous continuons ici notre série d'articles sur la programmation en Perl. Ce langage puissant et riche permet d'écrire rapidement des programmes utiles et fiables. Sa conformité POSIX en fait un allié indispensable à l'administrateur système ; la grande facilité de mise en oeuvre de ses fonctions et la souplesse de sa syntaxe sont très appréciées des développeurs de logiciels.
Voici donc le deuxième article de notre série introductive au langage. Depuis la dernière fois, les scalaires et les expressions de base n'ont plus aucun secret pour vous. Des notions plus complexes et des fonctions plus puissantes sont alors à notre portée. C'est le cas des listes, des tableaux et de l'impressionnant arsenal de fonctions Perl permettant de les manipuler ; vous verrez tout ce qu'il est possible de faire en Perl avec ces deux concepts a priori anodins.
Une liste est une suite (donc ordonnée) de valeurs scalaires. Nous verrons comment créer une liste, la manipuler, la parcourir etc.
Une variable de type tableau peut contenir plusieurs valeurs scalaires. Cette notion est présente dans de nombreux langages de programmation et ne posera sans doute problème à personne.
Les passerelles entre listes et tableaux sont nombreuses et très intuitives en Perl. C'est pour cela que nous n'entrerons pas ici dans les détails de la distinction entre liste et tableau. Un prochain article vous expliquera précisément ces différences. Pour le moment, j'utiliserai chacun des deux termes à bon escient sans forcement indiquer explicitement pourquoi j'utilise l'un plutôt que l'autre, mais les notions pourront apparaître naturelles au lecteur sans qu'il ne soit nécessaire de préciser les choses pour le moment.
En Perl, une liste peut être représentée par les valeurs qu'elle doit contenir
encadrées par un couple de parenthèses. Par exemple (2,5,-3)
est une liste de 3 scalaires : 2, 5 et -3. Autre exemple
(2,'age',"Bonjour $prenom")
est aussi une liste ; en effet
les listes contenant des scalaires, rien ne nous empêche d'en constituer une
comportant des nombres et des chaînes de caractères mêlés.
La liste vide se représente sous la forme suivante : ()
L'opérateur d'intervalle ..
permet de créer une liste comportant
des valeurs successives entre deux bornes. La liste (1..10)
comporte tous les entiers de 1 à 10 ; on aurait pu aussi écrire
(1,2,3,4,5,6,7,8,9,10)
, mais cette dernière notation est bien
plus lourde. Il faut savoir que les valeurs des bornes ne doivent pas obligatoirement
être des nombres : par exemple, la liste ('a'..'z')
comporte toutes les
lettres de l'alphabet, en minuscule et dans l'ordre. Il est aussi possible
de spécifier les bornes à l'aide de variables : ($debut..$fin)
On comprendra qu'il n'est pas toujours possible de résoudre ce type de liste (par exemple
si $debut
vaut 1 et $fin
vaut 'a'
), dans ce cas la liste est vide.
Dernier exemple, la liste (1..10,"age","a".."z")
comporte 37 éléments (10+1+26).
La liste (1,2,("nom",12),"aaa",-1)
n'est pas une liste à 5 éléments dont le troisième serait une autre liste,
c'est en fait une liste à 6 éléments. On aurait pu écrire
(1,2,"nom",12,"aaa",-1)
et on aurait obtenu la même liste.
On appelle cela l'aplatissement (ou la linéarisation) des listes.
Pour constituer une liste de listes, il faudra faire usage de références
(notion que nous aborderons plus tard).
L'opérateur de répétition (x
), que l'on a déjà appliqué
aux chaînes de caractères la fois dernière, s'applique aussi aux listes :
(2,10) x 3
est une liste à 6 éléments valant
(2,10,2,10,2,10)
.
Pour simplifier les choses, un tableau est une variable qui peut avoir
une liste pour valeur. Une telle variable se déclare de la sorte :
my @t;
On a alors un tableau vide, c'est-à-dire
sans élément. Pour lui donner une valeur lors de sa déclaration, il faut
faire ainsi :
my @t = (3,'chaine',"bonjour $prenom");
On a alors déclaré ce tableau en l'initialisant au moyen d'une liste.
On peut accéder directement à un élément d'un tableau grâce à son indice :
$t[
indice]
représente l'élément d'indice indice du tableau @t
.
Notez bien que la globalité du tableau se représente au moyen d'une arobase
@t
alors qu'un élément particulier est désigné
à l'aide d'un dollar $t[
indice]
,
cette dernière expression étant bien une variable de type scalaire
(le dollar est réservé aux scalaires en Perl).
Les indices des tableaux en Perl commencent à 0 (comme en C), ce qui signifie
que le premier élément du tableau @t
est $t[0]
et le deuxième $t[1]
etc. Voici un petit exemple d'utilisation
de tableau :
my @t = (3,5); # déclaration et initialisation $t[1] = 4; # affectation d'un élément print "$t[0]\n"; # affichage d'un élément
Il est intéressant de savoir
qu'il est possible d'accéder au dernier élément d'un tableau en utilisant
l'indice -1 : $t[-1]
est le dernier élément de @t
.
De la même façon, $t[-2]
est l'avant-dernier, etc.
Il est possible de connaître l'indice du dernier élément d'un tableau @t
grâce à la variable $#t
On a donc $t[$#t]
équivalent à $t[-1]
(ce dernier étant bien plus lisible).
Il peut être utile de savoir que l'expression scalar(@t)
(c'est-à-dire l'utilisation d'un tableau en contexte scalaire)
donne le nombre d'éléments du tableau @t
(ce qui vaut 1 de plus que $#t
) ;
$x=@t
donnerait la même chose.
Il faut savoir que vous ne générerez pas d'erreur (débordement ou autre)
si vous tentez d'accéder à un élément au-delà du dernier. La valeur de
cet élément sera simplement undef
et le programme
continuera. Depuis la version 5.6 de Perl, l'instruction
exists
(que l'on retrouvera pour les tables de
hachage) permet de tester l'existence d'un élément d'un tableau :
if( exists( $t[100] ) ) { ... }
Ce test sera vrai si l'élément d'indice 100 du tableau @t
existe. Ce qui est différent du test suivant :
if( defined( $t[100] ) ) { ... }
Car on teste ici si l'expression $t[100]
vaut undef
ou non, ce qui peut être vrai dans deux cas : soit l'élément existe
et vaut undef
, soit l'élément n'existe pas ...
Voici une autre illustration du fait que vous n'avez pas à vous soucier de problèmes d'allocation mémoire :
my @t = (3,23.4,"as"); $t[1000] = 8;
Ce programme est correct et fonctionne parfaitement : l'affectation
à l'indice 1000 agrandit le tableau d'autant ... Les éléments d'indice
compris entre 3 et 999 valent undef
et
scalar(@t)
vaut 1001. C'est si facile finalement !
Un tableau qu'il est utile de connaître est @ARGV
.
Cette variable spéciale est toujours définie (même dans les fonctions)
et ne nécessite pas de déclaration. Elle contient les arguments de la ligne
de commande du programme. Les trois façons de lancer un programme en Perl
sont succeptibles d'utiliser @ARGV
:
perl -w -e '... code perl ...' arg1 arg2 arg3 perl -w script.pl arg1 arg2 arg3 ./script.pl arg1 arg2 arg3
Ces trois programmes sont lancés avec les trois mêmes arguments.
Sachez que, contrairement au langage C, le nom du programme
n'est pas contenu dans @ARGV
qui ne comporte
donc que les arguments au sens strict. La variable spéciale
$0
(comme en shell) contient le nom du programme
(nul besoin de déclarer cette variable pour l'utiliser).
Il est possible d'affecter un tableau à un autre tableau en une seule instruction :
@t = @s;
Cette instruction copie le tableau @s
dans le tableau @t
. Le tableau @t
perd ses anciennes valeurs, prend celle de @s
et sa
taille devient celle de @s
:
on obtient bien deux tableaux tout à fait identiques (et distincts,
la modification de l'un n'entraînant nullement la modification de l'autre).
Voici d'autres instructions d'affectation mêlant tableaux et listes :
($a,$b) = (1,2);
Cette instruction affecte une valeur à chacune des variables
de la liste de gauche : $a
reçoit 1
et $b
reçoit 2.
($a,$b) = (1,2,3);
Les mêmes affectations sont effectuées ici, la valeur 3
n'étant d'aucune utilité.
($a,$b) = (1);
L'affectation à $a
de la valeur 1
est effectuée et $b
est mis à
undef
(son ancienne valeur est
perdue).
($a,$b) = @t;
Les variables citées à gauche reçoivent les premières valeurs
du tableau @t
:
$a
en reçoit le premier élément
ou undef
si @t
est vide ;
$a
reçoit le deuxième élément
ou undef
si @t
il ne
contient qu'un élément.
@t = (1,2);
Cette instruction réinitialise le tableau @t
(dont les anciennes valeurs sont toutes perdues, y compris
celles d'indice différent de 0 et 1) en lui affectant les valeurs
de droite : on obtient donc un tableau à deux éléments.
($a,$b) = Fonction();
Nous verrons un peu plus loin comment écrire une fonction,
et comment lui faire renvoyer une liste : ici l'affectation
se fait dans les mêmes conditions que pour les trois premiers cas.
($a,$b) = ($b,$a);
Cette instruction est la plus savoureuse : on peut échanger
deux variables Perl sans avoir à en utiliser une troisième ...
(Ai-je déjà dit que Perl s'occupe lui-même de la mémoire ?)
Pour déclarer plusieurs variables avec un seul my
,
le débutant aurait tendance à écrire la chose suivante (il n'y a pas
de honte !) :
my $a,$b; # Incorrect !
Ceci est incorrect. Pour pouvoir faire cela, il nous faut utiliser une liste :
my ($a,$b);
Les variables $a
et $b
sont créées et valent undef
. Pour leur affecter
des valeurs, il faut là aussi utiliser une liste (ou un tableau) :
my ($a,$b) = (1,2); my ($c,$d) = @t;
Les mêmes règles que pour l'affectation de listes s'appliquent ici.
On retrouve la notion d'aplatissement des listes avec les tableaux :
@t = (1,2,"age"); @t2 = (10,@t,20);
Le tableau @t2
ne comporte pas trois
éléments dont celui du milieu serait lui-même un tableau, mais contient
les cinq éléments, résultat de l'aplatissement du tableau
@t
dans la liste de droite lors de l'affectation
de @t2
. Cette affectation a eu le même résultat
qu'aurait eu la suivante :
@t2 = (10,1,2,"age",20);
La syntaxe suivante est intéressante à connaître :
($a,@t) = @s;
Le membre gauche de l'affectation est constitué d'une liste
comportant une variable scalaire et un tableau. Il n'y a pas à
proprement parler d'aplatissement de liste car il s'agit ici
d'une l-value (membre gauche d'une affectation), mais
la variable $a
reçoit le premier élément
du tableau @s
et le tableau @t
absorbe tous les autres (@s
n'étant bien-sûr pas modifié).
En fait dans cette syntaxe, le premier tableau rencontré dans la
liste de gauche reçoit tous les éléments restant de la liste de droite.
D'éventuelles autres variables qui le suivraient (cas idiot, mais bon...)
serait mises à undef
s'il s'agit de scalaires
et à vide s'il s'agit de tableaux. Par exemple, l'affectation suivante :
@s = (10,1,2,"age",20); ($a, @t, @u, $b) = @s;équivaut à :
@s = (10,1,2,"age",20); $a = 10; @t = (1,2,"age",20); @u = (); $b = undef;
foreach
Cette instruction permet de parcourir une liste. Son implémentation
optimisée dans l'interpréteur Perl rend son usage bien plus efficace
qu'un parcours qui utiliserait une variable indicielle incrémentée
à chaque tour d'une boucle for
. Sa syntaxe est la suivante :
foreach
variable (
liste ) {
instructions }
À chaque tour de boucle, la variable aura pour valeur un élément de la liste, la liste étant parcourue dans l'ordre. Aucune modification ni suppression dans la liste n'est effectuée par défaut dans ce type de boucle. Il vous est possible de modifier la variable de boucle (ce qui aura pour effet de modifier l'élément en question), mais, par défaut, le parcours n'est pas destructif.
Par exemple :
foreach $v (1,43,"toto") { print "$v\n"; }
Ce petit programme affiche chaque élément de la liste sur une ligne. Ces autres exemples sont valides eux-aussi :
foreach $v (@t) { .... } foreach $v (32,@t,"age",@t2) { .... }
Dans le premier cas, les éléments du tableau @t
sont parcourus.
Le second exemple illustre les phénomènes d'aplatissement
des listes qui se retrouvent ici aussi.
Il est possible de déclarer la variable de boucle dans le foreach
de la manière suivante :
foreach my $v (@t) { print "$v\n"; }
Il est aussi possible de ne pas utiliser explicitement de variable
de boucle ; dans ce cas c'est la variable spéciale $_
qui sera automatiquement utilisée :
foreach (@t) { print "$_\n"; }
Comme pour les autres boucles, l'instruction next
passe à la valeur suivante sans exécuter les instructions qui la suivent dans le bloc.
L'instruction last
met fin à la boucle.
Voici un petit exemple d'utilisation de foreach
affichant des tables de multiplication :
#!/usr/bin/perl -w use strict; die("Usage: $0 <n> <n>\n") if( !defined( $ARGV[1] ) ); foreach my $i (1..$ARGV[0]) { foreach my $j (1..$ARGV[1]) { printf( "%4d", $i*$j ); } print "\n"; }
Et le voici à l'oeuvre :
./mult.pl Usage: ./mult.pl <n> <n> ./mult.pl 5 3 1 2 3 2 4 6 3 6 9 4 8 12 5 10 15
Il existe de nombreuses fonctions permettant de manipuler les tableaux.
Pour chacun des exemples qui vont suivre, je suppose que nous
avons un tableau @t
déclaré de la sorte :
my @t = (1,2,3,4);
Ajout et suppression à gauche
La fonction shift
prend un tableau
en argument ; elle supprime son premier élément (les autres
sont alors décalés) et renvoie cet élément :
$v = shift(@t);
$v
vaut alors 1 et @t
la liste (2,3,4).
La fonction unshift
prend en arguments
un tableau et une liste de valeurs scalaires ; ces valeurs
sont ajoutées au début du tableau :
unshift(@t,5,6);
@t
vaut alors la liste (5,6,1,2,3,4).
Ajout et suppression à droite
La fonction pop
prend un tableau
en argument ; elle supprime son dernier élément et renvoie cet
élément :
$v = pop(@t);
$v
vaut alors 4 et @t
la liste (1,2,3).
La fonction push
prend en argument
un tableau et une liste de valeurs scalaires ; ces valeurs
sont ajoutées à la fin du tableau :
push(@t,5,6);
@t
vaut alors la liste (1,2,3,4,5,6).
Inversion
En contexte de liste,
la fonction reverse
prend en argument
une liste et renvoie la liste inversée, c'est-à-dire celle dont
les éléments sont pris dans le sens opposé :
@s = reverse(@t);
@s
vaut alors la liste (4,3,2,1)
et @t
n'est pas modifiée.
push
pour ajouter un élément
et pop
pour en prendre un.
De façon analogue, une file est un endroit où le premier entré
est le premier à sortir (first in-first out) comme pour une file
à une caisse de magasin. On peut par exemple utiliser les fonctions
push
pour ajouter un élément et
shift
pour en prendre un.
D'autre manipulations plus complexes du contenu d'un tableau sont
possibles avec la fonction splice
, mais je
vous renvoie à la documentation pour les détails.
qw
L'opérateur qw
nous permet de créer facilement une
liste de chaînes de caractères. En effet, il peut sembler pénible de constituer
une longue liste de tels éléments en raison du fait qu'il faut
délimiter chacun d'entre eux au moyen de simples ou de doubles quotes :
@t = ( 'Ceci', 'est', 'quelque', 'peu', 'pénible', 'à', 'écrire', ',', 'non', '?' );
Avec qw
, ceci devient tout à coup plus lisible :
@t = qw(Cela est bien plus facile à faire non ?);
La chaîne de caractère sera découpée selon les espaces, les tabulations et les retours à la ligne.
Les délimiteurs les plus souvent utilisés sont les parenthèses (comme dans l'exemple précédent) ainsi que les slashs :
@t = qw/Ou alors comme cela .../;
Cette fonction est bien pratique mais peut être source d'erreurs, voyez l'exemple suivant :
@t = qw/ attention 'aux erreurs' bêtes /;
Les simples quotes (') semblent indiquer que le programmeur souhaite
constituer un seul élément comportant les mots aux
et erreurs
; ce n'est pas ce qui est fait ici.
En effet, ni les simples quotes ni les doubles quotes ne constituent un
moyen de regrouper des mots pour l'opérateur qw
.
La liste ainsi créée comporte donc 4 éléments ; on aurait pu écrire :
("attention","'aux","erreurs'","bêtes")
.
Une fonction est un ensemble d'instructions regroupées de manière à être utilisées plusieurs fois sans avoir à dupliquer du code.
Le mot-clef sub
permet de définir des fonctions en Perl.
Les arguments d'une fonction sont des valeurs scalaires, à l'exclusion
de toutes autres (on verra comment faire en sorte de passer un tableau
en argument) ; ces paramètres sont accessibles via la variable
spéciale @_
(qui est donc un tableau). Modifier
une valeur de @_
modifiera les variables d'appel,
il est donc d'usage d'en faire une copie avant manipulation.
sub maJolieFonction { my ($x,$y,$t) = @_; ... instructions ... return $z; }
Ces quelques lignes définissent une nouvelle fonction dont le nom est
maJolieFonction
. Cette fonction copie dans trois
variables locales les trois premières valeurs du tableau
@_
, c'est-à-dire ses trois premiers paramètres
(les règles classiques d'affectation entre listes et tableaux s'appliquent ici).
Je vous conseille de toujours commencer vos fonctions par une ligne
copiant les valeurs de @_
et de ne plus utiliser
@_
dans la suite de la fonction (sauf cas spécial).
Si votre fonction attend un seul paramètre, la syntaxe peut être la suivante :
my ($x) = @_;
mais ne peut pas être :
my $x = @_; #incorrect
La syntaxe suivante peut aussi être utile :
my ($x,@t) = @_;
le tableau @t
reçoit tous les paramètres restants.
Enfin, une autre écriture que vous verrez souvent dans les programmes Perl est la suivante :
my $x = shift;
celle-ci s'appuie sur le fait que dans une sous-routine, la fonction
shift
travaille par défaut sur
@_
.
L'instruction return
met fin à l'exécution de la
fonction et on peut lui fournir une expression qui sera alors la
valeur de retour de la fonction.
La fonction ainsi définie peut être appelée au moyen de la syntaxe suivante :
maJolieFonction(10,20,30);
Dans ce cas, l'éventuelle valeur de retour est ignorée. Pour récupérer cette valeur :
$v = maJolieFonction(10,20,30);
Il est possible d'omettre les parenthèses lors de l'appel à une fonction :
maJolieFonction 10,20,30; # À éviter
mais cela peut créer des ambiguïtés et je vous déconseille donc cette syntaxe.
S'il est possible en Perl d'imposer le nombre d'arguments pour une fonction
(nous n'en parlerons pas ici), cela n'est pas fait par défaut.
Rien ne nous empêche en effet d'appeler la fonction maJolieFonction
précédemment définie avec deux ou quatre arguments, alors qu'elle
semble en attendre trois ; si on l'appelle avec deux arguments, la variable
$t
vaudra undef
; par contre
si on l'appelle avec plus de trois arguments, les valeurs suivantes
seront ignorées. Mais cette particularité du langage est parfois bien
pratique, notamment pour écrire des fonctions à nombre variable d'arguments.
Les variables déclarées au moyen de my
dans une fonction ne seront visibles qu'à l'intérieur même de la fonction,
dans le code qui suit la déclaration.
Dans une fonction, il est possible d'accéder aux variables définies
à la 'racine' du programme (c'est-à-dire en dehors de toute fonction) : il s'agit
donc de variables globales. Si une variable locale a le même nom
qu'une variable globale, cette dernière est masquée par la variable locale :
my $a = 3; my $b = 8; my $c = 12; sub maJolieFonction { my $a = 5; print "$a\n"; # affiche 5 print "$b\n"; # affiche 8 $c = 15; # modification de la variable globale print "$c\n"; # affiche 15 } maJolieFonction(); print "$a\n"; # affiche 3 print "$b\n"; # affiche 8 print "$c\n"; # affiche 15
De manière plus générale, les variables déclarées au moyen de my
sont visibles jusqu'à la fin du plus petit bloc qui les englobe. En particulier, dans
une fonction...
sub maJolieFonction2 { my $d = -3; if( ... ) { my $d = 4; my $e = 8; print "$d\n"; # affiche 4 print "$e\n"; # affiche 8 } print "$d\n"; # affiche -3 print "$e\n"; # $e n'existe pas ici }
Il est tout à fait possible de faire renvoyer plusieurs scalaires à une fonction, il suffit juste d'utiliser une liste. Voici des exemples de syntaxe de liste renvoyée par des fonctions :
return ($x,$z); return @t;
Dans le second cas, le tableau est converti en liste. Et voici comment il est possible de récupérer ces valeurs :
@s = fonction(...); ($j,$k) = fonction(...);
Ces deux manières de procéder peuvent parfaitement être utilisées
chacune dans les deux cas de return
pré-cités
(ce sont toujours les mêmes règles d'affectation qui s'appliquent).
Voici un exemple complet de programme en Perl avec une fonction :
1: #!/usr/bin/perl -w 2: use strict; 3: my $t = "Bonjour Larry"; # variable globale 4: print "$t\n"; # avec ou sans parenthèses 5: sub f 6: { 7: my ($x,$z) = @_; # deux arguments attendus 8: my $m = $x*$z; 9: printf("%d\n", $m); 10: return ($x+$z,$m); # retourne une liste 11: } 12: my @t = f(3,5); 13: print "$t $t[0] $t[1]\n";
Un bon programme Perl commence toujours par les première et deuxième lignes.
Si la variable scalaire $t
, elle, est globale,
en revanche les variables $x
, $z
et
$m
sont locales à la fonction.
En ligne 12, le tableau @t
reçoit pour valeur la liste renvoyée
par la fonction. Notez bien qu'il n'y a aucun conflit entre les variables
$t
et @t
; en effet, l'instruction de la
dernière ligne procède d'abord à l'affichage de la variable scalaire
$t
puis du premier et deuxième éléments du
tableau @t
(les crochets permettent de savoir
qu'il s'agit d'éléments d'un tableau).
Voici un exemple de fonction. Elle est récursive (c'est-à-dire qu'elle fait appel à elle-même) : nous allons calculer la factorielle d'un nombre. Par définition, F(0)=F(1)=1 et F(n)=n*F(n-1) pour tout n supérieur à 1 :
sub Fact { my ($n) = @_; return 1 if( $n == 1 || $n == 0 ); return $n * Fact($n-1); } print Fact(5)."\n"; # affiche 120
Vous savez maintenant manipuler des tableaux et des listes, vous savez aussi écrire des fonctions : vous voila armés pour aborder les très puissantes et bien pratiques fonctions de gestion qui suivent.
join
La fonction join
prend en paramètre un
scalaire et une liste ; elle renvoie une chaîne de caractères
comportant les éléments de la liste, concaténés et séparés
par ce premier paramètre scalaire. Les arguments passés ne sont pas
modifiés.
scalaire = join(
séparateur,
liste );
Voici quelques exemples :
$s = join(" ",1,2,3);
La variable $s
vaut alors la chaîne "1 2 3"
.
$s = join(',',$x,$y,$y);
Les valeurs des trois variables sont jointes en les alternant avec des virgules.
Le résultat est affecté à $s
.
$s = join(" : ",@t);
La variable vaut alors la concaténation des valeurs du tableau @t
avec " : "
pour séparateur.
split
La fonction split
prend en paramètre un
séparateur et une chaîne de caractères ; elle renvoie la liste
des éléments de la chaîne de caractères délimités par le séparateur.
Le séparateur est une expression régulière, notion que nous aborderons
lors d'un prochain article, mais dont le minimum de connaissances suffit à cette
fonction ; admettez ici qu'une telle expression est à placer entre
slashs (/
).
Les arguments passés ne sont pas modifiés.
liste = split( /
séparateur/,
chaîne );
Voici quelques exemples :
@t = split(/-/,"4-12-455");
Le tableau comporte alors les éléments 4, 12 et 455.
($x,$y) = split(/==/,$v);
Les deux variables auront pour valeur les deux premières chaînes
de caractères qui soient séparées par deux signes d'égalité.
print join(':',split(/ /,'salut ici'));
Affiche salut:ici
(il existe des méthodes
plus efficaces et plus lisibles de faire cela...).
sort
La fonction sort
prend en paramètre un bloc
d'instructions optionnel et une liste ; elle renvoie une liste triée conformément
au critère de tri constitué par le bloc d'instructions.
La liste passée en argument n'est pas modifiée.
liste2 = sort(
liste1 );
liste2 = sort( {
comparaison }
liste1 );
(attention à ne pas mettre de virgule entre le bloc d'instructions
et la liste)
Tout au long du tri, le bloc d'instruction sera évalué pour comparer
deux valeurs de la liste ; ces deux valeurs sont localement affectées
aux variables spéciales $a
et $b
qui ne sont définies que dans le bloc et sur lesquels il faut donc
effectuer la comparaison.
Il faut faire particulièrement attention au fait que s'il existe des
variables $a
et $b
dans le programme
elles seront localement masquées par ces variables spéciales
(source courante d'erreurs). Le bloc doit être composé d'une expression
dont la valeur est :
positive, si $a
doit être avant $b
dans la liste résultat.
négative, si $b
doit être avant $a
.
nulle, s'ils sont équivalents.
C'est là qu'entrent en jeu les opérateurs de comparaison cmp
et <=>
: ils permettent de comparer respectivement
les chaînes de caractères selon l'ordre lexical et les nombres selon l'ordre
numérique. Si la fonction sort
est appelée sans bloc
d'instruction, la liste est triée selon l'ordre lexical.
Voici quelques exemples :
@s = sort( {$a cmp $b} @t );
La liste @s
a pour valeur la liste
@t
triée selon l'ordre lexical.
@s = sort( @t );
Le fonctionnement est identique à l'exemple précédent.
@s = sort( {$a <=> $b} @t );
Le critère de tri est ici numérique.
@s = sort( {$a <=> $b or $a cmp $b} @t );
Une expression composée peut bien sûr servir de critère :
le tri est ici d'abord numérique puis lexical.
Cela permet de placer '8 navets' avant '12 carottes', et '12 carottes'
avant '12 navets'.
@s = sort( { fonction($a,$b) } @t );
Vous pouvez écrire votre propre fonction de tri (à deux
arguments) ; elle doit renvoyer un nombre dont la valeur
dépend de l'ordre voulu (voir juste avant).
grep
La fonction grep
prend en paramètre un
critère de sélection et une liste ; elle renvoie la liste des éléments
correspondant au critère.
La liste passée en argument n'est pas modifiée.
Le critère de sélection peut être soit une expression régulière (cas sur lequel nous reviendrons plus tard), soit un bloc d'instructions (cas sur lequel nous allons nous étendre) :
liste2 = grep {
sélection }
liste1;
(attention : pas de parenthèses ni de virgule)
Les éléments renvoyés sont ceux pour lesquels l'évaluation du bloc
d'instructions a pour valeur vrai. Durant cette évaluation, chacune des valeurs sera
localement affectée à la variable spéciale $_
sur laquelle les tests devront donc être effectués.
Voici quelques exemples :
@t = grep { $_<0 } $x,$y,$z;
Affecte à @t
les éléments négatifs de la liste.
@s = grep { $_!=8 and $_!=4 } @t;
Met dans @s
les éléments de
@t
différents de 4 et de 8.
@s = grep { fonction($_) } @t;
Vous pouvez écrire votre propre fonction de sélection ;
elle doit renvoyer vrai ou faux selon si l'élément
est à garder ou non.
En contexte scalaire, la fonction grep
renvoie le nombre d'éléments qui correspondent au critère :
$n = grep { .... } @t;
La syntaxe de grep
comportant une expression
régulière est la suivante :
liste2 = grep( /
regexp/,
liste1 );
En quelques mots, les éléments renvoyés seront ceux qui correspondront
à l'expression régulière. Par exemple @s = grep( /^aa/, @t );
affecte à @s
les éléments de @t
qui commencent par deux lettres a
. Plus d'explications
sur les expressions régulières seront données dans un prochain article.
J'ai affirmé que la liste d'origine n'était pas modifiée,
mais il vous est possible de le faire. Si, durant la sélection, vous
affectez une valeur à $_
, la liste sera modifiée.
Mais cela est sans doute une mauvaise idée de modifier la liste
passée en paramètre d'un grep
car la fonction
map
est faite pour cela.
map
La fonction map
prend en paramètre un bloc
d'instructions et une liste ; elle applique le bloc
à chacun des éléments de la liste (modification possible de la liste)
et renvoie la liste constituée des valeurs successives de l'expression évaluée.
liste2 = map( {
expression }
liste1 );
(attention à ne pas mettre de virgule entre le bloc d'instructions
et la liste)
La variable spéciale $_
vaut localement (dans le bloc
d'instructions) chaque élément de la liste. La valeur de la dernière
expression du bloc sera placée dans la liste résultat.
Voici quelques exemples :
@s = map( { -$_ } @t );
Le tableau @s
aura pour valeurs les opposés
des valeurs de @t
.
@p = map( { $_."s" } @t );
Tous les mots de @t
sont mis au pluriel
dans @p
.
@s = map( { substr($_,0,2) } @t );
Le tableau @s
aura pour valeurs les deux
premiers caractères des valeurs de @t
.
@s = map( { fonction($_) } @t );
Vous pouvez écrire votre propre fonction ;
les valeurs qu'elle renverra seront placées dans @s
.
Dans les exemples qui précèdent, la liste d'origine n'est pas modifiée (sauf dans le dernier exemple où elle peut l'être dans la fonction). Voici un exemple de modification de liste :
map( { $_*=4 } @t );
Tous les éléments de @t
sont multipliés par quatre.
Nous allons ici illustrer l'usage des listes et des tableaux par un exemple mathématique : le crible d'Ératosthène. Cet algorithme permet de calculer tous les nombres premiers inférieurs à un nombre donné n.
Son principe est le suivant : nous construisons tout d'abord la liste de tous les entiers de 2 à n. Ensuite, à chaque itération, nous supprimons de la liste tous les multiples du premier nombre de la liste et signalons ce premier nombre comme étant premier. Au premier tour de boucle, je supprime tous les nombres pairs et dis que 2 est premier. Au suivant, je supprime tous les multiples de 3 et affirme que 3 est premier. Au tour suivant, c'est le 5 qui est au début de la liste (4 étant multiple de 2, il a déjà été supprimé), j'enlève de la liste les multiples de 5 et annonce la primalité de 5 etc ... L'algorithme se termine lorsque la liste est vide, j'ai alors déterminé tous les nombres premiers inférieurs à n.
Voici la fonction réalisant cet algorithme en Perl :
sub Crible { my ($n) = @_; # Liste initiale : my @nombres = (2..$n); # Liste des nombres premiers trouvés : my @premiers = (); # Tant qu'il y a des éléments (un tableau # en contexte booléen vaut faux s'il est vide) : while( @nombres ) { # On extrait le premier nombre my $prem = shift @nombres; # On indique qu'il est premier push @premiers, $prem; # On supprime ses multiples @nombres = grep { ( $_ % $prem )!=0 } @nombres; } # On renvoie la liste des nombres premiers return @premiers; }
Quiconque a déjà réalisé cet algorithme en C ou C++ comprendra la joie que cette concision procure ...
Nous voilà au terme de ce deuxième article. Vous maîtrisez maintenant les listes et les tableaux ; ces deux notions ont été abordées dans leurs nombreux aspects. De la même façon, vous êtes maintenant en mesure d'écrire des fonctions et de profiter de la souplesse de Perl dans ce domaine.
Lors de la prochaine étape nous aborderons les tables de hachage, un outils très précieux en Perl et souvent incompris des débutants et donc peu utilisé, car inexistant dans la plupart des autres langages.