Formation Perl - Formateur Perl

Formation de qualité par un spécialiste Perl

Sylvain Lhullier

Version 1.4.5
mars 2023


Guide Perl - Débuter et progresser en Perl

Dernière version sur  https://formation-perl.fr/guide-perl.html
© 2002-2023 Sylvain Lhullier - Permission est accordée de copier et distribuer ce document sans modification et à condition de fournir un lien vers la page https://formation-perl.fr/guide-perl.html

> Envie d'une formation Perl ? Sylvain Lhullier propose des formations au langage Perl, contactez-le.

4. Listes et tableaux

Les scalaires et les expressions de base n'ont maintenant 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. Dans ce document, 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 formellement.

4.1. Valeurs de listes

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 trois 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 minuscules 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 à cinq éléments dont le troisième serait une autre liste, c'est en fait une liste à six é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 précédemment, s'applique aussi aux listes : (2,10) x 3 est une liste à six éléments valant (2,10,2,10,2,10).

4.2. Manipulation de tableaux

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. De manière plus explicite, voici comment déclarer un tableau vide :

my @t = ();

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 susceptibles d'utiliser @ARGV:

perl -e '... code perl ...' arg1 arg2 arg3
perl 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).

4.3. Affectations

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 celles 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 ; $b 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 ?).

4.4. Multi-déclaration

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.

4.5. Retour sur l'aplatissement des listes

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);

4.6. Absorption d'une liste par un tableau

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...) seraient 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;

Simple et intuitif.

4.7. La structure de boucle 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
use strict;
use warnings;
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'œuvre :

./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

Passons à la suite.

4.8. Fonctions de manipulation de tableaux

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 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).

    • 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).

  • Ajout et suppression à droite

    • 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).

    • 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).

  • 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.

Avec de telles fonctions, il est alors envisageable de manipuler des objets algorithmiques tels que les piles et les files. Une pile est un lieu de stockage ayant pour particularité que le dernier élément à y être entré sera le premier à en sortir (last in-first out) comme pour une pile d'assiettes sur une étagère de placard. On peut utiliser pour cela un tableau, avec les fonctions 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'autres manipulations plus complexes du contenu d'un tableau sont possibles avec la fonction splice, mais je vous renvoie à la documentation pour les détails.

4.9. L'opérateur 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ères 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 quatre éléments ; on aurait pu écrire : ("attention","'aux","erreurs'","bêtes").

4.10. Joindre les éléments dans une chaîne avec 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.

4.11. Découper une chaîne de caractères en liste avec split

La fonction split prend en paramètres 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 dans la suite, 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...).

4.12. Trier une liste avec sort

La fonction sort prend en paramètres 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'instructions 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 lesquelles 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 :

  • négative, si $a doit être avant $b dans la liste résultat ;

  • positive, 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'instructions, 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( {length($b) <=> length($a) 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 inverse sur la longueur puis lexical. Cela permet d'effectuer un second tri pour les éléments égaux selon le critère du premier tri.

  • @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).

4.13. Sélectionner des éléments avec grep

La fonction grep prend en paramètres 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 que 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 la suite.

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.

4.14. Appliquer un traitement à tous les éléments avec map

La fonction map prend en paramètres 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.


Contact © 1999-2024 formation-perl.fr
Formation Perl - Formation Perl intra entreprise - Formation Perl inter entreprise - Formateur Perl - Formateur Perl délégation
Formation Perl Formateur Perl Appartement à louer à Noisy-le-Grand Bric-à-Brac