Formation Perl - Formateur Perl

Formation de qualité par un spécialiste Perl

Sylvain Lhullier

Version 1.3.20
septembre 2016


Guide Perl - débuter et progresser en Perl

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

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

6. Tables de hachage

Les tables de hachage de Perl ne se retrouvent pas dans beaucoup d'autres langages ; pour les avoir souvent utilisées en Perl, il est dur de repasser à des langages qui n'en sont pas pourvus.

Une table de hachage (hash table en anglais) est un type de donnée en Perl permettant d'associer une valeur à une clef. On peut dire d'un tableau (notion abordée précédemment) qu'il associe une valeur scalaire à un entier : à la position i (pour i entier), une certaine valeur scalaire est présente. Une table de hachage va nous permettre d'aller au-delà : on pourra faire correspondre une valeur scalaire (comme pour un tableau) à toute chaîne de caractères (plutôt qu'à un entier).

Je peux, par exemple, avoir envie de gérer en Perl un index téléphonique simple : chacun de mes amis a un numéro de téléphone, je veux pouvoir retrouver leur numéro à partir de leur prénom. Je vais donc associer le numéro au prénom :

"Paul"     -> "01.23.45.67.89"
"Virginie" -> "06.06.06.06.06"
"Pierre"   -> "heu ..."

Les prénoms seront les clefs, c'est-à-dire le "point d'entrée" dans la table de hachage (comme les indices numéraux le sont pour les tableaux). Les numéros de téléphone seront les valeurs associées à ces clefs. Il s'agit bien d'une association chaîne de caractères vers scalaire.

Vous l'avez sans doute compris, dans une table de hachage, une clef n'est présente qu'une seule fois et ne peut donc avoir qu'une seule valeur (comme l'élément d'un indice donné d'un tableau). Par contre, une valeur peut être associée à plusieurs clefs.

6.1. Déclaration et initialisation

Une variable de type table de hachage se déclare de la sorte :

my %h;

On a alors une table de hachage vide (aucune clef). Il est possible de signaler explicitement que l'on déclare une table de hachage vide :

my %h = ();

Pour donner des valeurs initiales à notre table de hachage, on peut utiliser la syntaxe suivante :

my %h = ( "Paul"     => "01.23.45.67.89",
          "Virginie" => "06.06.06.06.06",
          "Pierre"   => "heu ..." );

Cette dernière table de hachage est déclarée et initialisée avec les clefs Paul, Virginie et Pierre ayant respectivement pour valeurs 01.23.45.67.89, 06.06.06.06.06 et heu ...

6.2. Accéder à un élément

Dans une table de hachage %h, on peut accéder à la valeur d'une clef au moyen de la syntaxe suivante : $h{clef} ; par exemple $h{Paul} vaut 01.23.45.67.89. Si la clef comporte d'autres caractères que des lettres, des chiffres et le souligné (underscore en anglais '_'), il faut la délimiter au moyen de simples ou de doubles-quotes : $h{"Marie-Pierre"} ou $h{'Marie-Pierre'}.

En fait, cette syntaxe force un contexte de chaîne de caractères entre les accolades, ce qui fait qu'un mot simple (bareword en anglais) sera converti silencieusement en chaîne de caractères (même en positionnant le pragma use warnings;).

De façon similaire aux tableaux avec l'arobase (@t), la totalité d'une table de hachage se représente au moyen du signe pourcentage (%h), alors qu'une valeur particulière est désignée à l'aide d'un dollar $h{clef}, cette dernière expression étant bien une variable de type scalaire.

Voici quelques exemples de manipulation d'éléments de la table de hachage %h :

$h{Jacques} = "02.02.02.02.02";
print "Tél : $h{Jacques}\n";
$h{'Jean-Paul'} = "03.03.03.03.03";
if( $h{"Jean-Paul"} ne "Heu ..." ) {
   ...
}

La clef utilisée pour cette syntaxe peut tout à fait être contenue dans une variable scalaire (qui sera évaluée en contexte de chaîne de caractères) :

my $k = "Jacques";
$h{$k} = "02.02.02.02.02";

Elle peut même être une expression plus complexe :

sub f { return "Jac"; }
$h{f().'ques'} = "02.02.02.02.02";

6.3. Parcours

Il existe trois fonctions permettant de parcourir une table de hachage. Dans les exemples fournis, nous considérerons que la table %h a été déclarée ainsi :

my %h = ( "Paul"     => "01.23.45.67.89",
          "Virginie" => "06.06.06.06.06",
          "Pierre"   => "heu ..." );
  • keys : obtenir une liste des clefs

    Cette fonction prend en paramètre une table de hachage et renvoie une liste comportant toutes les clefs de la table. L'ordre des clefs est quelconque, seule l'exhaustivité des clefs est garantie.

    my @t = keys(%h);
    

    Le tableau @t peut par exemple valoir la liste ("Virginie", "Pierre", "Paul").

    Cette fonction va nous permettre de parcourir toute la table de hachage en effectuant une boucle sur la liste des clefs :

    foreach my $k (keys(%h)) {
       print "Clef=$k Valeur=$h{$k}\n";
    }
    

    La variable de boucle $k prendra pour valeurs successives l'ensemble des clefs de la table, l'expression $h{$k} est la valeur associée à la clef $k. Ce petit programme affichera donc tous les couples clef/valeur de la table %h.

  • values : obtenir une liste des valeurs

    De la même façon que keys renvoie une liste des clefs d'une table de hachage, la fonction values fournit une liste des valeurs ; pour cette fonction non plus, l'ordre n'est pas garanti et seule l'exhaustivité l'est.

    L'exemple suivant

    foreach my $v (values(%h)) {
       print "Valeur=$v\n";
    }
    

    affichera tous les numéros de téléphone (c'est-à-dire les valeurs) de la table %h.

    Il n'est bien sûr pas possible de retrouver la clef des valeurs que l'on manipule ainsi.

    Il peut être intéressant de savoir que l'ordre des clefs renvoyées par keys et celui des valeurs par values sera le même à condition de ne pas modifier la table de hachage entretemps.

  • each : itération sur les couples (clef,valeur)

    Cette fonction renvoie un à un tous les couples (clef,valeur) d'une table de hachage. Elle a un comportement un peu spécial du fait qu'il faut l'appeler autant de fois qu'il y a de couples : c'est une fonction avec état, c'est-à-dire qu'elle ne renvoie pas toujours la même chose d'un appel à l'autre : en effet, elle renvoie le couple suivant ! De ce fait, je vous conseille de toujours l'utiliser dans la syntaxe qui suit :

    while( my ($k,$v) = each(%h) ) {
       print "Clef=$k Valeur=$v\n";
    }
    

Libre à vous de parcourir vos tables de hachage avec la fonction qui vous convient le mieux.

6.4. Autovivification

Sous ce terme barbare se cache une idée simple : si vous tentez de modifier un élément d'une table de hachage qui n'existe pas, il sera créé. S'il est utilisé dans un contexte numérique, il prendra pour valeur initiale zéro. S'il est utilisé dans un contexte de chaîne de caractères, il prendra pour valeur la chaîne vide (depuis Perl 5.6).

Par exemple, considérons une table de hachage qui ne comporte pas la clef hello ; l'expression suivante

$h{hello} .= "après";

associe à la clef hello la valeur chaîne vide puis lui concatène la chaîne "après". De la même façon, l'expression

$h{bye}++;

crée un élément de valeur 1.

Cette propriété d'autovivification est bien pratique dans le cas où l'on ne connaît pas les clefs avant de devoir y accéder. Par exemple nous allons pouvoir compter le nombre d'occurrences des mots dans un texte de manière très simple. Supposons que les mots du texte soient déjà dans un tableau (par exemple en utilisant la fonction qw ; elle ne règle pas les problèmes des ponctuations, des majuscules et des lettres accentuées, mais elle suffira à notre exemple). Nous allons utiliser chaque mot comme une clef de la table et nous allons ajouter 1 à la valeur de cette clef :

my @texte = qw( bonjour vous bonjour );
my %comptage = ();
foreach my $mot ( @texte ) {
   $comptage{$mot}++;
}
while( my ($k,$v) = each(%comptage) ) {
   print "Le mot '$k' est présent $v fois\n";
}

Ce qui donne l'affichage suivant :

Le mot 'vous' est présent 1 fois
Le mot 'bonjour' est présent 2 fois

Dans la suite nous verrons comment découper un texte en mots au moyen des expressions régulières.

6.5. Existence et suppression d'une clef

À la lecture de ce qui précède, il peut sembler impossible de savoir si un élément d'une table de hachage existe ou non. Rassurez-vous, les auteurs de Perl ont tout prévu :-) L'opérateur exists renvoie vrai si l'élément de table de hachage qu'on lui donne en paramètre existe ; sinon il renvoie faux. Par exemple :

if( exists( $h{hello} ) ) {
   print "La clef 'hello' existe\n";
}

Il est important de noter qu'un test effectué au moyen de l'opérateur defined aurait été possible, mais dangereux. En effet, l'expression defined( $h{hello} ) est fausse dans deux cas très différents : soit si l'élément n'existe pas, soit si l'élément existe et vaut undef ; elle sera vraie si l'élément existe et ne vaut pas undef. Il est donc impossible de distinguer le cas d'un élément absent et celui d'un élément indéfini (valant undef) avec defined.

Cette distinction entre absent et indéfini peut paraître artificielle dans ce cas (elle peut tout de même être importante dans certaines situations !), mais dans le cas de la suppression d'une clef, il en est tout autrement.

Pour supprimer une clef dans une table de hachage, il faut utiliser l'opérateur delete. L'instruction

delete( $h{hello} );

supprime la clef hello de la table %h si elle existe (si elle n'existe pas, elle ne fait rien). De la même façon que exists est la bonne méthode pour tester l'existence d'un élément, delete est la bonne méthode pour en supprimer un. Le débutant pourrait être tenté d'écrire :

$h{hello} = undef;  # attention!

Ce qui est fort différent, car dans ce cas, la clef hello aura une valeur indéfinie, mais existera toujours ! On la retrouvera, par exemple, dans les parcours effectués au moyen des opérateurs keys, values ou each ; ce qui n'est sans doute pas le but recherché.

Pour résumer, on peut dire que pour tester l'existence d'une clef, il faut utiliser exists et que pour en supprimer une, il faut utiliser delete.

En marge de ces deux fonctions, voici une manière de savoir si une table de hachage est vide ou non (on qualifie de vide une table de hachage qui ne comporte aucune clef). Cette syntaxe utilise la table de hachage en contexte de chaîne de caractères, par exemple de cette façon :

if( %h eq 0 ) {
   print "%h est vide\n";
}

La valeur d'un hachage en contexte scalaire n'a pas d'autre utilisation que celle-ci. En effet, scalar(%A) renvoie une valeur du type 4/8 qui indique le nombre de places (buckets en anglais) utilisées par rapport au nombre total disponible dans le hachage. Une table vide est un cas particulier, elle renverra 0.

6.6. Tables de hachage et listes

On peut facilement passer d'une liste (ou tableau) à une table de hachage et inversement. Voyez, par exemple, le petit programme suivant :

my @t = ("Paul", "01.23.45.67.89", "Virginie",
         "06.06.06.06.06", "Pierre", "heu ...");
my %h = @t;

La première instruction crée un tableau @t initialisé à une liste à six éléments. La seconde crée une table de hachage %h initialisée au moyen du précédent tableau. Les valeurs du tableau sont prises deux à deux : la première de chaque couple sera la clef dans la table de hachage, la seconde la valeur. Si le nombre d'éléments de la liste est impair, la dernière clef créée aura undef pour valeur. Si une clef venait à être présente plusieurs fois dans la liste, c'est la dernière valeur qui sera prise en compte dans la table de hachage.

On aurait aussi pu écrire :

my %h = ("Paul", "01.23.45.67.89", "Virginie",
         "06.06.06.06.06", "Pierre", "heu ...");

Il est à noter que cette syntaxe rappelle étrangement l'un des premiers exemples de création de table de hachage qui utilisait => pour séparer clefs et valeurs. Cette similarité est en fait une quasi-équivalence, car l'opérateur => peut être utilisé à la place de la virgule pour créer des listes ; il n'a été ajouté au langage Perl que pour faciliter la lecture des affectations de tables de hachage, car il force un contexte de chaîne à sa gauche, ce qui permet justement d'écrire %a = ( toto => 'titi' );

La conversion dans l'autre sens est aussi possible. L'évaluation d'une table de hachage dans un contexte de liste renvoie une liste des clefs et des valeurs, se suivant respectivement deux à deux, dans un ordre quelconque entre couples. La table de hachage %h de l'exemple précédent peut être affectée à un tableau :

my @t2 = %h;

Le tableau @t2 sera initialisé, par exemple, avec la liste suivante : ("Pierre", "heu ...", "Paul", "01.23.45.67.89", "Virginie", "06.06.06.06.06") ; chaque clef précède sa valeur, mais l'ordre des couples (clef,valeur) est quelconque (un peu comme pour la fonction each).

Une table de hachage se convertit en liste sans encombre dès qu'elle est en contexte de liste. Je vous laisse deviner ce que fait le code suivant :

foreach my $x (%h) {
   print "$x\n";
}

La fonction reverse, qui nous a permis d'inverser les listes, peut être employée pour inverser une table de hachage :

%h = reverse(%h);

Les valeurs deviennent les clefs et inversement. Si plusieurs valeurs identiques sont présentes, le comportement est imprévisible, car certes, lors de la transformation de liste en table de hachage la dernière valeur compte, mais lors de la transformation de table de hachage en liste l'ordre est quelconque...

L'association individu - numéro de téléphone est idéale pour illustrer cela :

my %h = ("Paul", "01.23.45.67.89", "Virginie",
         "06.06.06.06.06", "Pierre", "heu ...");
my %quidonc = reverse %h;

On pourra alors retrouver la personne à partir de son numéro de téléphone. Si, par contre, Paul et Virginie avaient eu le même numéro, on n'aurait pas pu prédire quelle serait la personne renvoyée.

6.7. Exemples

Voici quelques exemples d'utilisation des tables de hachage.

Le premier concerne la variable spéciale %ENV qui contient les variables d'environnement du programme. $ENV{PATH} contient le path, $ENV{HOME} vaut le nom du répertoire personnel de l'utilisateur qui exécute le programme, etc.

Deuxième exemple, les tables de hachage peuvent servir à constituer des tableaux à plusieurs dimensions ; on pourrait en effet imaginer avoir des clefs qui seraient la concaténation des coordonnées dans les n dimensions : dim1:dim2:dim3:...

my %h = ();
foreach my $i (0..4) {
   foreach my $j (-3..10) {
      foreach my $k (130..148) {
         $h{"$i:$j:$k"} = Calcul($i,$j,$k);
      }
   }
}

Nous verrons dans la suite qu'il est possible de bâtir de réels tableaux à plusieurs dimensions en utilisant des références.

L'exemple suivant concerne les ensembles ; nous allons utiliser les tables de hachage pour calculer l'union et l'intersection de deux ensembles.

# Voici mes deux ensembles
# Je mets les éléments dans des tableaux
my @ensA = (1, 3, 5, 6, 7, 8);
my @ensB = (2, 3, 5, 7, 9);

# Voici mon union et mon intersection,
# les éléments des ensembles en seront les clefs
my %union = ();
my %inter = ();

# Je mets tous les éléments de A dans l'union :
foreach my $e (@ensA) { $union{$e} = 1; }

# Pour tous les éléments de B :
foreach my $e (@ensB) {

    # S'il est déjà dans l'union, c'est qu'il est
    # dans A : je le mets donc dans l'intersection :
    $inter{$e} = 1 if ( exists( $union{$e} ) );

    # Je le mets dans l'union
    $union{$e} = 1;
}

# Tous les éléments présents dans A ou B
# sont des clefs de la table union.
# Tous les éléments présents dans A et B
# sont des clefs de la table inter.

# Je reconstitue des tableaux à partir
# des tables de hachage (en les triant
# pour l'affichage)
my @union = sort( {$a<=>$b} keys(%union) );
my @inter = sort( {$a<=>$b} keys(%inter) );

print("@union\n");
# affiche : 1 2 3 5 6 7 8 9
print("@inter\n");
# affiche : 3 5 7

Pour le même problème, voici une solution n'utilisant qu'une seule table de hachage, je vous laisse le soin d'en apprécier le principe :

my @ensA = (1, 3, 5, 6, 7, 8);
my @ensB = (2, 3, 5, 7, 9);

my %hash = ();  # Qu'une seule table ...

foreach my $e (@ensA) { $hash{$e}++; }
foreach my $e (@ensB) { $hash{$e}++; }

my @union = sort( {$a<=>$b} keys(%hash) );
my @inter = sort( {$a<=>$b}
                  ( grep { $hash{$_}==2 } keys(%hash) )
                );

print("@union\n");
# affiche : 1 2 3 5 6 7 8 9
print("@inter\n");
# affiche : 3 5 7

La compréhension de cet exemple demande d'avoir assimilé plusieurs notions importantes vues jusqu'ici.

6.8. Tranches de tableau

Maintenant que nous connaissons tous les types de données de Perl, notamment les tableaux et les tables de hachage, nous allons voir comment on peut manipuler plusieurs éléments d'un tableau ou d'une table de hachage à la fois. Cela s'appelle une tranche (slice en anglais).

Une tranche de tableau est un sous-ensemble des éléments du tableau. Imaginons par exemple un tableau @t duquel nous souhaiterions manipuler les éléments d'indice 4 et 10 ; pour cela nous allons prendre la tranche correspondante de ce tableau : @t[4,10] est une liste à deux éléments qui est équivalente à ($t[4],$t[10]). Quelques explications sur la syntaxe. Tout d'abord, l'expression commence par une arobase, car il s'agit d'une liste d'éléments ; le dollar est réservé aux scalaires, par exemple $t[4] est un scalaire. Ensuite, comme d'habitude pour les tableaux, les crochets permettent de spécifier les indices. Enfin, l'ensemble des indices est indiqué par une liste d'entiers : @t[2,10,4,3] @t[3..5] @t[fonction()]...

Une telle tranche est utilisable comme valeur (passage de paramètres, etc.) et comme l-value (expression à gauche du signe égal d'affectation) :

@t[4,10] = (4321,"age");

cette instruction affecte 4321 à l'indice 4 du tableau @t et la chaîne age à l'indice 10. On aurait pu écrire

($t[4],$t[10]) = (4321,"age");

Une autre utilisation des tranches de tableau apparaît avec les fonctions qui renvoient une liste. Par exemple la fonction stat prend en paramètre un nom de fichier et renvoie toutes sortes d'informations sur le fichier : taille, dates, propriétaire etc. Il est courant d'écrire :

($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,
 $atime,$mtime,$ctime,$blksize,$blocks) = stat($filename);

La fonction renvoie une liste qui est affectée aux variables de la liste de gauche. Les tranches peuvent intervenir si seules quelques informations vous intéressent et que vous ne voulez pas déclarer de variables inutiles. Par exemple, si seules les dates de modification (indice 9) et de création (indice 10) vous intéressent, vous pouvez écrire :

($mtime,$ctime) = ( stat($filename) )[9,10];

L'appel à la fonction est placé entre parenthèses et on ne prend que les éléments d'indice 9 et 10 de sa valeur de retour. On a alors une liste à deux éléments, celle-ci est affectée à la liste à gauche du signe égal et donc ces deux éléments sont affectés aux deux variables.

6.9. Tranches de table de hachage

De la même façon qu'il existe des tranches pour les tableaux et les listes, il en existe pour les tables de hachage. La sélection s'effectue bien sûr sur les clefs. Par exemple, si %h est une table de hachage, alors @h{'clef1','clef2'} est une liste équivalente à ($h{'clef1'},$h{'clef2'}) Il est ensuite possible d'utiliser cette liste comme bon vous semble (affectation, passage en paramètre, etc.).

Une utilisation (assez complexe) des tranches serait indiquée lorsque l'on veut construire automatiquement une liste de valeurs uniques à partir d'un tableau dont on n'est pas sûr que ses valeurs soient uniques :

# Un tableau avec des valeurs dupliquées :
my @t = qw(hello toto hello vous);

# Déclaration d'une table de hachage :
my %h;

# On prend la tranche de %h dont les clefs
# sont les valeurs du tableau @t
# et on leur associe la valeur undef
@h{@t} = ();

# Les clefs de %h sont donc constituées des
# valeurs de @t, et on est sûr de ne les
# retrouver qu'une seule fois :
@t = keys %h;

Le tableau @t comporte alors une fois et une seule chacun de ses éléments.


Contact © 1999-2017 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