Dans ce cours, nous passons en revue les éléments syntaxiques du langage Perl en parallèle avec le langage de description vu en cours d'algorithmique (variables, types, structures de contrôle, procédures et fonctions, etc.). En particulier quand Perl propose plusieurs syntaxes à une même fin, nous choisissons systématiquement la plus rigoureuse, la moins ambigüe, bref, la plus proche du cours d'algorithmique.
Puis, nous nous intéressons à l'utilisation de Perl pour le traitement des textes, structurés ou non (lecture/écriture de fichiers, opérations sur les chaînes de caractères, expressions régulières, production de code HTML ou de code LaTeX, etc.). Dans la partie travaux pratiques, nous nous intéressons en particulier aux applications en linguistique, sociologie, open data, visualisation de données, etc.
Instructions, expressions, variables et types Perl
Programmes, blocs d'instructions et instructions
Commençons par un programme Perl minimal, en particulier pour expliciter les premières lignes que nous retrouverons systématiquement :
use strict;
use warnings;
use utf8;
binmode(STDOUT,":utf8");
binmode(STDERR,":utf8");
# premier affichage
print "Bonjour le monde !\n";
Commentaires sur ce code Perl :
- La première ligne précise où se trouve l'interpréteur Perl qui va traiter la suite du programme (le chemin indiqué ici est valable sur les systèmes Linux, le fichier pourra être rendu exécutable et appelé dans un terminal).
- La deuxième indication stipule que nous utilisons Perl en mode strict, ce qui oblige par exemple à déclarer préalablement les variables utilisées.
- Ensuite nous demandons à Perl de signaler les éventuels warnings et de nous donner des diagnostics clairs en cas de problème.
- Les trois lignes suivantes permettent de travailler en UTF-8 (voir le cours de codage si nécessaire) : dans le fichier Perl, sur la sortie standard (STDOUT) et sur le canal d'erreur (STDERR).
- Notons également le caractère # qui permet d'introduire un commentaire qui pour nous clarifie la programmation mais que Perl ignorera.
- L'affichage est réalisé à l'aide l'instruction print. Cette instruction est particulièrement importante : c'est elle qui va afficher le résultat final de tout programme. \n provoque un passage à la ligne.
Autres contraintes syntaxiques de base :
- en Perl, les blocs d'instructions sont délimités par des accolades : { représente un Début de bloc, } une Fin ;
- chaque instruction est impérativement suivie d'un ; (point-virgule).
Types des variables, variables, assignation
Nous retrouvons en Perl les types les plus simples vus en cours d'algorithmique : booléens, entiers, réels, caractères et chaînes de caractères, etc. Ils sont regroupés en Perl sous le terme de scalaire.
Concernant les booléens en Perl, il n'y a pas de valeurs booléennes à proprement parler :
- les valeurs 0 (l'entier zéro) et '' (la chaîne de caractères vide) sont condidérées comme faux.
- toutes les autres valeurs de type simple sont interprétées en vrai.
En Perl, toutes les variables commencent par un signe particulier : $ pour les variables scalaires.
L'affectation d'une valeur à une variable se fait avec le symbole = (égal), à ne pas confondre avec le test d'égalité entre nombres symbolisée par == (double égal).
Lors de la première utilisation d'une variable, nous utiliserons le mot-clef my en guise de déclaration de variable (mais pas de déclaration de type en Perl). Nous pouvons donc écrire par exemple :
my $msg; # déclaration sans affectation
my $x = 2;
my $pi = 3.14;
my $prenom = 'toto';
Opérateurs (arithmétiques, booléens, etc.) et comparateurs en Perl
Nous disposons en Perl des opérateurs arithmétiques classiques : +, -, *, / et de l'opérateur de concaténation entre chaînes de caractères noté par . (un point).
my $i = 10; $i = $i + 1; $i = $i + 9; my $texte = 'La variable i vaut '.$i; $texte = "$texte\n";
Remarquons ici que Perl n'a pas de contrôle fort sur les types des variables : une même variable peut subir une opération arithmétique puis une opération propre aux chaînes de caractères sans provoquer d'erreur. Au final, la variable $texte contient donc le texte « La variable i vaut 20\n ».
Enfin, Perl propose :
- des opérateurs booléens : && (le ET), || (le OU), ! (la négation) ;
- des comparateurs entre entiers : ==, !=, <, >, <=, >= ;
- des comparateurs (alphabétiques) entre chaînes de caractères : eq, ne, lt, gt, le, ge.
La précision use locale indique d'utiliser les paramètres régionaux pour comparer alphabétiquement deux mots, sans se tromper ni sur les mots accentués ni sur les règles locales. Elle influence donc les comparateurs qui opèrent sur les chaînes de caractères. Essayons par exemple :
my $mot1 = 'évaluer'; my $mot2 = 'fermer'; if ($mot1 lt $mot2) { # est-ce que « évaluer » est avant « fermer » ? print "« $mot1 » est avant « $mot2 ».\n"; } else { print "« $mot2 » est avant « $mot1 ».\n"; } use locale; # utilisation des règles locales... donc françaises ? if ($mot1 lt $mot2) { # devrait placer « évaluer » avant « fermer » print "« $mot1 » est avant « $mot2 ».\n"; } else { print "« $mot2 » est avant « $mot1 ».\n"; }
Chaînes de caractères en Perl
Basiques sur les chaînes de caractères en Perl
En Perl, les chaînes de caractères sont délimitées soit par des apostrophes, soit par des guillemets. Dans le premier cas, le contenu de la chaîne n'est pas interprété par Perl. Dans le second cas, le contenu est interprété : une variable apparaissant dans la chaîne est remplacée par sa valeur et différents codes peuvent modifier la mise en forme de l'affichage.
my $msg = 'coucou'; my $nb = 5; print '$nb fois $msg\n'; print "$nb fois $msg\n";
Dans cet exemple, le premier affichage produira « $nb fois $msg\n », le second lui affichera « 5 fois coucou » avec un passage à la ligne ensuite (codé par \n).
Dans une chaîne de caractères entre guillemets, nous pourrons aussi placer \t (pour une tabulation), \U (pour passer la suite de la chaîne en majuscules), \u (pour passer la lettre suivante en majuscule), \L (pour passer la suite de la chaîne en minuscules) et \l (pour passer la lettre suivante en minuscule).
Opérations Perl sur les chaînes de caractères
Rappelons d'abord les comparateurs sur les chaînes de caractères : eq, ne, lt, gt, le, ge.
Ajoutons ci-dessous les principales opérations sur les chaînes de caractères :
- length : obtenir la longueur ;
- symbole . : concaténer deux chaînes de caractères ;
- substr : extraire et substituer ;
- index et rindex : rechercher la position d'une sous-chaîne ;
- chop : enlever le dernier caractère d'une chaîne ;
- chomp : enlever l'éventuel caractère de passage à la ligne en fin de chaîne.
Attention : les positions des caractères dans une chaîne sont comptées à partir de 0.
Exemples :
my $verbe = 'chanter'; print length($verbe); # affiche la valeur 7 my $premiere = substr($verbe,0,1); # récupération de la première lettre my $terminaison = substr($verbe,-2,2); # récupération de la terminaison du verbe substr($verbe,-2,2,'ons'); # le verbe devient « chantons » my $mot = <STDIN>; # saisie d'un mot sur l'entrée standard et Entrée chop $mot; # on enlève le retour chariot pour ne garder que le mot
Structures de contrôle en Perl
Nous retrouvons les structures de contrôle habituelles mais dans la syntaxe propre à Perl. Ainsi le si alors sinon s'exprime comme suit :
if (une condition ici) { # des instructions ici } else { # des instructions ici }
La boucle tant que prend la forme générale suivante :
while (une condition ici) {
# des instructions ici
}
ce qui donne sur un exemple comptant de un à dix :
my $i=0; # initialisation d'une variable $i while ($i<10) { # tant que et sa condition print "$i\n"; # affichage $i = $i+1; # incrémentation de la variable $i }
La boucle pour, elle, nécessite la définition de trois éléments sur sa première ligne, séparés par des ; (points-virgules) :
for ( initialisation ; condition pour continuer ; progression) {
# des instructions ici
}
Sur le même exemple que précédemment, nous constatons que cette boucle for reprend de manière plus compacte les trois éléments observés dans une boucle while :
for (my $i=0 ; $i<10 ; $i++) { print "$i\n"; }
Une autre boucle (foreach) existe en Perl mais est dédiée spécifiquement au parcours de tableaux.
Structures de données Perl : les tableaux classiques
Dans cette section, nous décrivons les tableaux dits classiques. En Perl,
- il n'y a pas de contrainte sur le contenu des cases ;
- les cases sont numérotées à partir de zéro ;
- les noms des variables de type tableau débutent par le symbole @ (arobase).
Tableaux classiques Perl : définition et accès
Commençons par l'affectation de valeurs aux cases d'un tableau.
# définition d'un tableau vide et remplissage des cases my @notes = (); $notes[0] = 13; $notes[1] = 8; $notes[2] = 12; $notes[3] = 17; # syntaxe équivalente my @notes = (13,8,12,17); # ajout d'un élément en fin de tableau push(@notes,14);
Les symboles @ (arobase) et () (parenthèses) sont donc dorénavant pour nous le signal d'un tableau.
La taille d'un tableau (c'est-à-dire son nombre de cases) est fournie par la fonction scalar.
# affichage de la taille d'un tableau
print scalar(@notes);
Plus exactement, scalar force l'interprétation du tableau dans un contexte scalaire et, en Perl, un tableau vu comme un entier vaut donc le nombre de cases du tableau. En conséquence, () (tableau vide) vaut faux dans un contexte booléen et un tableau non-vide vaut vrai.
Pour obtenir le contenu d'une case choisie au hasard, nous utilisons la fonction rand :
my @notes = (13,8,12,17,14); # définition d'un tableau print $notes[int(rand(scalar(@notes)))]; # affichage d'une case au hasard
Explications : rand va fournir un nombre réel choisi au hasard entre zéro et la taille du tableau, nous en prenons ensuite la partie entière avec int, ce qui nous donne un numéro d'une case dans le tableau.
Nous pouvons ordonner les cases d'un tableau avec l'instruction sort. Sans autre précision le tri est alphabétique. Si un autre comportement est attendu, il faut préciser comment Perl doit comparer deux valeurs quelconques $a et $b.
# définition d'un tableau de nombres et d'un tableau de mots my @notes = (13,8,12,17,14); my @verbes = ('voler','chanter','danser'); # tri alphabétique des deux tableaux my @snotes = sort @notes; my @sverbes = sort @verbes; # affichage des nombres : 12 13 14 17 8 :-( foreach my $note (@snotes) { print "$note "; } print "\n"; # affichage des verbes :-) foreach my $verbe (@sverbes) { print "$verbe "; } print "\n"; # tri numérique du tableau de nombres et affichage ok @snotes = sort {$a <=> $b} @notes; foreach my $note (@snotes) { print "$note "; } print "\n";
Parcours de tableaux Perl avec les boucles for et foreach
La boucle for nous permet déjà de parcourir un tableau Perl :
for (my $i=0 ; $i<scalar(@notes) ; $i++) {
# des instructions ici portant sur la ie note : $notes[$i]
}
La boucle foreach elle, est dédiée au parcours de tableaux. Elle est plus simple si l'on accepte de se concentrer sur les valeurs contenues dans les cases, sans avoir besoin des numéros des cases.
foreach my $note (@notes) {
# des instructions ici portant directement sur $note
}
Gestion des flux et des fichiers
Les instructions Perl relatives aux fichiers et aux flux sont les suivantes :
- open : créer un flux et l'associer à un fichier,
- close : fermer le flux et l'accès au fichier,
- binmode : spécifier l'encodage du flux,
- opérateur <FLUX> : récupérer une ligne dans le flux.
Les flux sont notés par des mots librement choisis, mais systématiquement écrits en majuscules.
La production d'un fichier commence par le symbole > dans l'ouverture du flux : cela signifie que le fichier est ouvert en écriture, il sera créé s'il n'existe pas et écrasé s'il existe déjà. Ensuite les écritures dans le flux se font avec le print habituel mais en précisant cette fois le flux. Sans la précision du flux, print écrit dans STDOUT.
open(OUT,">sortie.html"); # ouverture en écriture binmode(OUT,":utf8"); print OUT "<p>coucou !</p>\n"; # écriture dans le flux print OUT "<p>ça va ?</p>\n"; close(OUT);
Lecture d'un fichier :
open(IN,"dico.txt"); # ouverture en lecture binmode(IN,":utf8"); while (my $ligne = <IN>) { # lecture dans le flux,ligne par ligne chop($ligne) # suppression du retour chariot # traitements de la $ligne ici } close(IN);
À noter que ce dernier fonctionnement a déjà été vu avec l'entrée standard : le flux <STDIN>.
Procédures et fonctions en Perl
Parfois nous sommes amenés à répliquer un bloc de code particulièrement utile, il est alors de l'isoler dans une fonction ou un procédure, de le nommer et de réfléchir aux paramètres dont il a besoin.
Nous insistons sur la différence entre procédures et fonctions, même si elles sont introduites par le même mot-clef.
Syntaxe générale des procédures et fonctions en Perl
Procédures et fonctions sont introduites en Perl par le mot sub. Le schéma général est le suivant :
sub nom (signature des paramètres) {
# des instructions ici
}
Nous sommes libres de choisir les noms des procédures et fonctions.
Les paramètres ne sont pas précisés par des noms de variables, mais par le symbole de leur type : $, @ ou %. Les valeurs sont transmises dans un tableau noté @_ dont nous prendrons soin d'immédiatement extraire et nommer les valeurs. Par exemple la procédure suivante attend deux paramètres scalaires et nous les nommons $a et $b :
sub nom_de_la_fonction ($$) { my ($a,$b) = @_; # récupération et nommage des paramètres # des instructions ici }
Comme @_ est un tableau, même s'il ne contient aucun ou un seul élément, nous devons trouver des parenthèses dans la partie gauche de l'affectation.
La différence essentielle entre procédure et fonction est qu'une fonction renvoie un résultat alors qu'une procédure ne renvoie rien. Nous déclions cette différence dans les exemples suivants.
Procédures et fonctions en Perl sur un exemple
Une procédure effectue un traitement et, éventuellement, produit des affichages. Ci-dessous une procédure qui prend une lettre en entrée, teste s'il s'agit ou non d'une voyelle et affiche un message en conséquence.
# définition d'une procédure sub est_voyelle_procédure_disjonction ($) { # récupération de la lettre à tester my ($lettre) = @_; # tests et affichage du résultat if (($lettre eq 'a') || ($lettre eq 'e') || ($lettre eq 'i') || ($lettre eq 'o') || ($lettre eq 'u') || ($lettre eq 'y')) { print "« $lettre » est une voyelle.\n"; } else { print "« $lettre » N'est PAS une voyelle.\n"; } } # appels à la procédure est_voyelle_procédure_disjonction('a'); est_voyelle_procédure_disjonction('b');
Ci-dessous le même traitement mais la réponse n'est plus affichée, elle est renvoyée à l'aide de l'instruction return. En conséquence, l'appel à la fonction peut (doit ?) être placée dans la partie droite d'une affectation ou dans la condition d'un if ou d'un while.
# définition d'une fonction sub est_voyelle_fonction_disjonction ($) { my ($lettre) = @_; if (($lettre eq 'a') || ($lettre eq 'e') || ($lettre eq 'i') || ($lettre eq 'o') || ($lettre eq 'u') || ($lettre eq 'y')) { return 1; # on renvoie vrai } else { return 0; # on renvoie faux } } # appels à la fonction # résultat de la fonction utilisé dans un test if (est_voyelle_fonction_disjonction('a')) { print "« a » est une voyelle.\n"; } else { print "« a » N'est PAS une voyelle.\n"; } # résultat de la fonction stocké dans une variable my $est_voyelle = est_voyelle_fonction_disjonction('b'); if ($est_voyelle) { print "« b » est une voyelle.\n"; } else { print "« b » N'est PAS une voyelle.\n"; }
Dans le cas du test des voyelles, les deux formulations peuvent sembler équivalentes, voire la procédure peut être jugée plus simple d'utilisation. Cependant, dans le cas d'un traitement plus sophistiqué qui utiliserait plusieurs fois est_voyelle comme brique de base, celle-ci ne pourrait être qu'une fonction et pas une procédure.
Expressions régulières en Perl
Contexte formel des expressions régulières
Les expressions régulières font partie des formalismes qui permettent de représenter des langages. Avec les expressions régulières, nous capturons les langages de type 3 de la hiérarchie de Chomsky : ce sont les langages les plus pauvres, et en conséquence, ce sont aussi ceux pour lequels nous pouvons tester rapidement si une chaîne de caractères fait oui ou non partie du langage visé.
Syntaxe des expressions régulières en Perl
Dans une expression régulière, il est d'abord possible d'utiliser les caractères. En Perl, nous pouvons y ajouter les quantifieurs classiques des expressions régulières :
zéro ou une fois | ? |
zéro ou plusieurs fois | * |
une ou plusieurs fois | + |
De plus, Perl fournit des caractères et des syntaxes particulières pour repérer des positions et des familles de caractères :
début | ^ | ||
fin | $ | ||
caractère quelconque | . | ||
un espace | \s | pas un espace | \S |
un chiffre | \d | pas un chiffre | \D |
une lettre | \w | pas une lettre | \W |
une ponctuation | [:punct:] | ||
une voyelle | [aeiouy] | pas une voyelle | [^aeiouy] |
Notons que \w est dépendant de la langue utilisée, son comportement sera lié à la locale déjà évoquée :
use locale;
Les textes caractérisés par les contraintes suivantes...
- un S au début ;
- suivi par une voyelle ;
- puis par au moins une lettre ;
- au moins un espace ;
- éventuellement des chiffres pour finir.
sont capturés par l'expression régulière ci-après :
^{}S[aeiouy]\w+\s+\d*$
Opérateurs Perl impliquant des expressions régulières
Pour confronter une chaîne de caractères à une expression régulière, nous disposons en Perl de l'opérateur de matching =~ //. Il trouvera sa place le plus souvent dans un test, comme dans cet exemple :
if ($verbe =~ /^[aeiou]/) { # est-ce que le verbe commence par une voyelle ?
...
}
Dans cet exemple, la voyelle initiale doit être en minuscule. Pour l'insensibilité à la casse, nous utilisons le modifieur i.
if ($verbe =~ /^[aeiou]/i) { # voyelle initiale en minuscule ou en majuscule
...
}
Perl permet aussi de repérer une partie de la chaîne de caractères, de l'extraire et de la récupérer dans une variable. La partie à extraire doit être placée entre parenthèses et Perl placera le contenu dans une variable numérotée.
$verbe =~ /(..)$/; # ciblage des deux dernières lettres $terminaison = $1; # récupération de la terminaison
Il est également possible en Perl d'opérer une substitution au sein de la chaîne de caractères, cela à l'aide de l'opérateur =~ s/// :
$verbe =~ s/er$/ons/; # le -er final est remplacé par -ons
Par défaut, une seule substitution est effectuée. Toutes les substitutions peuvent être opérées avec le modifieur g :
$description =~ s/\. /.\n/g; # ajout d'une passage à ligne après chaque point
Enfin, Perl fournit un opérateur de découpage basé sur les expressions régulières : split.
my ($num,$nom,$texte) = split(/,/,$ligne); # découpage selon les virgules my @lettres = split(//,$ligne); # découpage en caractères my @mots = split(/\W+/,$ligne); # découpage en mots my @morceaux = split(/(\W+)/,$ligne); # récupération des éléments et des séparateurs
Structures de données Perl : les tableaux associatifs
Après les tableaux classiques, nous nous intéressons aux tableaux dits associatifs. Ceux-ci ont la particularité que les cases ne sont plus indicées par un numéro mais par un texte. Les noms des variables de ce type débutent par %.
Vocabulaire, syntaxe et fonctions des tableaux associatifs
Selon le langage de programmation ou le contexte, ce que nous appelons tableau associatif peut être dénommé dictionnaire, table de hachage ou encore en anglais hash. Dans tous les cas, cela désigne une structure de données dans laquelle nous accédons très rapidement au contenu d'une case à partir de son nom.
Les textes qui indicent les cases sont appelés clefs et les contenus des cases valeurs.
Les noms des tableaux associatifs commencent donc par le symbole % et les noms des cases sont encadrés par des accolades : { et }. Sur l'exemple suivant, nous définissons un tableau associatif à vide, puis nous associons noms de personnes et adresses mails :
my %mail = (); $mail{'torre'} = 'torre@univ.fr'; $mail{'toto'} = 'toto.machin@free.fr'; print 'Mail de toto = ',$mail{'toto'},"\n";
Une autre syntaxe utilisant les symboles => permet de créer le tableau associatif et en même temps de le remplir :
my %mail = ( 'torre' => 'torre@univ.fr', 'toto' => 'toto.machin@free.fr' ); print 'Mail de toto = ',$mail{'toto'},"\n";
Les fonctions utiles sur ces tableaux :
- keys récupère les clefs d'un tableau associatif et les place dans un tableau classique,
- values récupère de la même manière les valeurs du tableau associatif,
- exists teste si une clef existe dans le tableau associatif,
- delete efface une case,
- scalar n'est pas utile directement sur ce type de tableaux pour connaître mais pour cela nous appellerons scalar sur le résultat de keys.
Voici quelques exemples de mises en œuvre de ces opérations :
print scalar(keys %mails)," personnes dans notre carnet.\n"; my @personnes = keys %mail; # récupération des clefs du tableau %mail if (exists($mail{'titi'})) { # nous testons l'existence d'une case print "on connaît titi et on a son mail.\n"; } else { print "titi est inconnu.\n"; }
Boucles pour le parcours des tableaux associatifs
Pour parcourir un tableau associatif, nous pouvons utiliser de la boucle foreach sur les clefs (keys) du tableau :
foreach my $clef (keys %tab) {
# des instructions ici utilisant $clef et $tab{$clef}
}
Alternativement, il est possible de conjuguer la boucle while avec la fonction each :
while (my ($clef,$val) = each %tab) {
# des instructions ici utilisant $clef et $tab{$clef}
}
Sortie triée des éléments d'un tableau associatif
La situation la plus simple est celle où l'on souhaite afficher les éléments du tableau associatif par ordre alphabétique des clefs. Il suffit alors d'appliquer sort aux clefs :
foreach my $clef (sort(keys %tab) {
# des instructions ici utilisant $clef et $tab{$clef}
}
Cas plus complexe : nous voulons ordonner selon les valeurs et non plus sur les clefs. Dans ce cas, il faut expliciter comment sort doit comparer deux valeurs $a et $b :
foreach my $nom (sort { $mail{$a} cmp $mail{$b} } (keys %mail) { # des instructions ici utilisant $nom et $mail{$nom} } foreach my $nom (sort { $age{$a} <=> $age{$b} } (keys %age) { # des instructions ici utilisant $nom et $age{$nom} }
cmp et <=> correspondent respectivement à la comparaison entre chaînes de caractères et à la comparaison entre nombres. Dans le premier cas, nous parcourons donc les personnes classées par ordre alphabétique de leurs mails, dans le second par ordre croissant de leurs âges.
Pour plus de lisibilité, nous pouvons isoler la comparaison des deux éléments dans une fonction et, pourquoi pas, stocker dans un tableau classique les clefs ordonnées selon leurs valeurs associées.
sub parage { $age{$a} <=> $age{$b}; } my @personnes_classées = sort parage (keys %age);
Utilisations de tableaux associatifs en Perl
Nous avons vu comment un tableau associatif pouvait stocker une information particulière sur des personnes (âge ou mail par exemple). Il est aussi possible d'y ranger plusieurs informations sur une même personne :
my %personne = ( 'pseudo' => 'Toto', 'mail' => 'toto.machin@free.fr', 'age' => '21', 'adresse' => '42 rue du Perl', 'ville' => 'Lille', );
Cependant, dans le cadre de nos applications au traitement automatique de la langue, nous allons détourner ces tableaux à d'autres fins.
Tester l'existence d'un mot dans un dictionnaire
Il s'agit par exemple de faire de la vérification orthographique. Dans ce cas, nous avons besoin d'un dictionnaire sous la forme d'une simple liste de mots. Si celle-ci est stockée dans un fichier, il suffit de la lire et de la charger dans un tableau associatif :
my %dictionnaire = ();
open(DICO,'dictionnaire.txt');
binmode(DICO,":utf8");
while (my $mot = <DICO>) {
chop $mot;
$dictionnaire{$mot} = 1; # création de la case avec un contenu quelconque
}
close(DICO);
Ici, les valeurs du tableau n'ont pas d'importance (nous avons mis toutes les cases à 1) puisque nous avons juste besoin de tester l'existence des clefs (qui sont les mots du dictionnaire) :
if (exists($dictionnaire{$mot})) { # on teste l'existence de la case
print "« $mot » est dans le dictionnaire.\n";
} else {
print "« $mot » est inconnu.\n";
}
Compter les occurrences d'un mot
Cette fois nous voulons avoir un compteur associé à chaque mot. Nous utilisons un tableau associatif avec les mots en clefs et les compteurs comme valeurs. À la rencontre d'un mot, nous allons mettre à jour son compteur comme suit :
if (exists($comptage{$mot})) { # occurrence supplémentaire $comptage{$mot} = $comptage{$mot} + 1; # on ajoute 1 au compteur } else { $comptage{$mot} = 1; # première occurrence on initialise la case à 1 }
Autrement dit : si le mot est déjà connu nous ajoutons 1 à son compteur et sinon nous notons que le mot a été rencontré une première fois.
Autres utilisations possibles des tableaux associatifs
Citons par exemple :
- compter des cooccurrences de mots,
- trouver les anagrammes d'un mot ou les mots possibles à partir d'un tirage au Scrabble,
- constituer un index qui à partir d'un mot donne les numéros des pages d'un livre dans lesquelles le mot apparaît.
Ces pistes nous obligent à augmenter encore nos structures de données car elles nécessitent des tableaux de tableaux. Cela implique de maîtriser les références en Perl, c'est le sujet abordé à la section suivante.
Références en Perl
Retour sur les procédures/fonctions Perl
Revenons sur quelques difficultés liées aux procédures et fonctions, difficultés que nous avons passées sous silence jusqu'à maintenant. Ces problèmes trouveront leurs solutions avec l'utilisation de références et nous permettrons d'aller vers les tableaux de tableaux.
Ce que reçoit une fonction est forcémment un et un seul tableau. Nous l'avons vu quand nous voulions simplement passer une unique valeur scalaire (il faut dans ce cas transmettre un tableau à une seule case...). Ce qui amène la question : comment passer plusieurs tableaux en paramètres à une procédure ? Exemple : procédure qui reçoit deux tableaux et dit lequel est le plus grand.
Perl interdit une procédure/fonction avec, par exemple, un profil @@. Nous pourrions renoncer aux profils, mais cela ne change pas réellement le problème : si l'on passe deux tableaux à une procédure, on les retrouve fusionnés au sein de l'unique tableau @_.
Idem pour les valeurs de retour des fonctions Perl : comment retourner plusieurs tableaux ? Exemple : fonction qui partitionne une liste de lettres en voyelles/consonnes et renvoie les deux tableaux obtenus.
Enfin, en Perl, les paramètres sont passés par valeur, autrement dit les procédures travaillent sur une copie des objets passés en paramètres. Ainsi, les modifications subies par les paramètres dans une procédure ne sont pas visibles de l'extérieur de la procédure, puisque ce sont les copies qui ont été modifiées, et pas les originaux.
Manipulations des références
La référence à une variable, ou son adresse, est un entier qui indique le numéro de la case en mémoire qui contient la valeur de la variable. Pour Perl, un tel entier est un object simple, un scalaire.
obtenir une référence selon l'objet
On peut obtenir l'adresse d'un objet en plaçant un \ devant une variable existante :
my $ref_mot = \$mot; my $ref_personnes = \@personnes; my $ref_dictionnaire = \%dictionnaire;
Il est aussi possible d'obtenir une telle référence lors de la création de la variable :
my $ref_personnes = []; # référence à un nouveau tableau classique, vide my $ref_dictionnaire = {}; # référence à un nouveau tableau associatif, vide
déréférencer selon l'objet pointé
Il s'agit de l'opération inverse, lorsque l'on veut retrouver l'objet pointé par une référence, à partir de cette référence. Cette fois on va ajouter $, @ ou % (selon le type de la variable) devant la variable qui contient la référence :
$$ref_mot @$ref_personnes %$ref_dictionnaire
Il est également possible d'accéder directement au contenu d'une case à partir de la référence au tableau, avec la notation -> :
print $ref_personnes->[2]; print $ref_dictionnaire->{'titi'};
Intérêts et applications des références
Les références constituent la solution aux problèmes que nous avons évoqués :
- Il est possible de passer plusieurs références à une procédure.
- Une fonction peut renvoyer plusieurs références.
- Si les objets donnés à une procédure sont susceptibles d'être modifiés et si nous souhaitons que ces modifications soient visibles de l'extérieur de la procédure, le passage doit se faire par référence, et non plus par copie.
- On peut avoir un tableau qui contient des références vers d'autres tableaux et ainsi manipuler des structures de données complexes comme des tableaux de tableaux.
Tableaux de tableaux en Perl
Construction d'un tableau de tableaux en Perl
Repartons d'un tableau associatif habituel...
my %scrabble = ( ... 'aegmnr' => 'manger'; ... );
... qui devient une tableau associatif contenant des tableaux classiques :
my %scrabble = (
...
'aegmnr' => [ 'gramen' , 'magner' , 'manger' ], # la clef est associée à l'adresse d'un tableau
...
);
Autre exemple, un tableau classique contenant des tableaux classiques :
my @contraintes = (
[ 'e' , 2 ], # chaque case du tableau contient une référence à un autre tableau
[ 'x' , 5 ],
);
Exemple d'un tableau classique de tableaux associatifs :
my @dictionnaire = (
...
{ 'mot'=>'manger' , 'definition'=>'prendre un repas' } # chaque case du tableau contient une référence à un tableau associatif
...
);
Tableaux de tableaux : accès aux cases et aux sous-tableaux
En poursuivant les exemples précédents :
my $lettre = $contraintes[1][0]; my $candidat = $scrabble{'aegmnr'}[2]; my @candidats = @{ $scrabble{'aegmnr'} } # accolades supplémentaires pour déréférencer un sous-tableau print $dictionnaire[12]{'definition'}; my %description = %{ $dictionnaire[12] }; # accolades supplémentaires pour déréférencer un sous-tableau