perlLoL - Manipulaton des Listes de Listes en Perl
La chose la plus simple à construire est une liste de listes (parfois appelée un tableau de tableaux). C'est raisonnablement facile à comprendre, et presque tout ce qui s'y applique pourra aussi être appliqué par la suite aux structures de données plus fantaisistes.
Une liste de listes, ou un tableau de tableaux si vous préférez, est juste
un bon vieux tableau @LoL auquel vous pouvez accéder avec deux
indices, comme $LoL[3][2]. Voici une déclaration du tableau :
# affecte a notre tableau une liste de references a des listes
@LoL = (
[ "fred", "barney" ],
[ "george", "jane", "elroy" ],
[ "homer", "marge", "bart" ],
);
print $LoL[2][2]; bart
Maintenant, vous devez faire bien attention au fait que les parenthèses extérieures sont bien des parenthèses et pas des accolades ou des crochets. C'est parce que vous affectez à une @list, vous avez donc besoin de parenthèses. Si vous n'aviez pas voulu que cela soit une @LdL, mais plutôt juste une référence à elle, vous auriez pu faire quelque chose du style de ceci :
# affecte une reference a une liste de references de liste
$ref_to_LoL = [
[ "fred", "barney", "pebbles", "bambam", "dino", ],
[ "homer", "bart", "marge", "maggie", ],
[ "george", "jane", "alroy", "judy", ],
];
print $ref_to_LoL->[2][2];
Notez que le type des parenthèses extérieures a changé, et donc notre
syntaxe d'accès aussi. C'est parce que, contrairement au C, en perl vous ne
pouvez pas librement échanger les tableaux et leurs références.
$ref_to_LoL est une référence à un tableau, tandis que
@LoL est un tableau proprement dit. De la même manière, $LoL[2] n'est pas un tableau, mais une réf. à un tableau. Ainsi donc vous pouvez
écrire ceci :
$LoL[2][2]
$ref_to_LoL->[2][2]
au lieu de devoir écrire ceci :
$LoL[2]->[2]
$ref_to_LoL->[2]->[2]
Eh bien, c'est parce que la règle est qu'entre des crochets adjacents (ou
des accolades), vous êtes libre d'omettre la flèche de déréférencement de
pointeur. Mais vous ne pouvez pas faire cela pour le tout premier si c'est
un scalaire contenant une référence, ce qui signifie que
$ref_to_LoL en a toujours besoin.
Tout ceci est bel et bien pour la déclaration d'une structure de données fixe, mais si vous voulez ajouter de nouveaux éléments à la volée, ou tout contruire à partir de zéro ?
Tout d'abord, étudions sa lecture à partir d'un fichier. C'est quelque
chose comme ajouter une rangée à la fois. Nous présumerons qu'il existe un
fichier tout simple dans lequel chaque ligne est une rangée et chaque mot
un élément. Voici la bonne façon de le faire si vous essayez de développer
une liste @LoL les contenant tous :
while (<>) {
@tmp = split;
push @LoL, [ @tmp ];
}
Vous auriez aussi pu charger tout cela dans une fonction :
for $i ( 1 .. 10 ) {
$LoL[$i] = [ somefunc($i) ];
}
Ou vous auriez pu avoir une variable temporaire traînant dans le coin et contenant la liste.
for $i ( 1 .. 10 ) {
@tmp = somefunc($i);
$LoL[$i] = [ @tmp ];
}
Il est très important que vous vous assuriez d'utiliser le constructeur de
référence de liste []. C'est parce que ceci serait très mauvais :
$LoL[$i] = @tmp;
Voyez vous, affecter comme ceci une liste nommée à un scalaire ne fait que compter le nombre d'éléments dans @tmp, ce qui n'est probablement pas ce que vous désirez.
Si vous fonctionnez sous use strict, vous devrez ajouter quelques déclarations pour qu'il soit content :
use strict;
my(@LoL, @tmp);
while (<>) {
@tmp = split;
push @LoL, [ @tmp ];
}
Bien sûr, vous n'avez pas du tout besoin de donner un nom au tableau temporaire :
while (<>) {
push @LoL, [ split ];
}
Vous n'êtes pas non plus obligé d'utiliser push(). Vous
pourriez juste faire une affectation directe si vous saviez où vous voulez
le mettre :
my (@LoL, $i, $line);
for $i ( 0 .. 10 ) {
$line = <>;
$LoL[$i] = [ split ' ', $line ];
}
ou même juste
my (@LoL, $i);
for $i ( 0 .. 10 ) {
$LoL[$i] = [ split ' ', <> ];
}
Vous devriez en général lorgner d'un regard mauvais l'usage de fonctions de liste potentielles dans un contexte scalaire sans le formuler explicitement. Ceci serait plus clair pour le lecteur de passage :
my (@LoL, $i);
for $i ( 0 .. 10 ) {
$LoL[$i] = [ split ' ', scalar(<>) ];
}
Si vous vouliez avoir une variable $ref_to_LoL comme référence
à un tableau, vous devriez faire quelque chose comme ceci :
while (<>) {
push @$ref_to_LoL, [ split ];
}
Maintenant vous pouvez ajouter de nouvelles rangées. Et pour ajouter de nouvelles colonnes ? Si vous traitez juste des matrices, le plus facile est souvent d'utiliser une simple affectation :
for $x (1 .. 10) {
for $y (1 .. 10) {
$LoL[$x][$y] = func($x, $y);
}
}
for $x ( 3, 7, 9 ) {
$LoL[$x][20] += func2($x);
}
Peu importe que ces éléments soient déjà là ou pas : elle les créera joyeusement pour vous, mettant les éléments intermédiaires à undef si besoin est.
Si vous vouliez juste ajouter à une rangée, vous auriez à faire quelque chose à l'air un peu plus bizarre :
# ajoute de nouvelles colonnes a une rangee existante
push @{ $LoL[0] }, "wilma", "betty";
Remarquez que je ne pourrais pas juste dire :
push $LoL[0], "wilma", "betty"; # FAUX !
En fait, cela ne se compilerait même pas. Pourquoi donc ? Parce que
l'argument de push() doit être un véritable tableau, et pas
juste une référence.
Maintenant il est temps de sortir votre structure de données. Comment allez-vous faire une telle chose ? Eh bien, si vous voulez uniquement l'un des éléments, c'est trivial :
print $LoL[0][0];
Si vous voulez sortir toute la chose, toutefois, vous ne pouvez pas dire :
print @LoL; # FAUX
car vous obtiendrez juste la liste des références, et perl ne déréférencera
jamais automatiquement les choses pour vous. Au lieu de cela, vous devez
vous faire tourner une boucle ou deux. Ceci imprime toute la structure, en
utilisant la construction for() dans le style du shell pour
boucler d'un bout à l'autre de l'ensemble des indices extérieur.
for $aref ( @LoL ) {
print "\t [ @$aref ],\n";
}
Si vous vouliez garder la trace des indices, vous pourriez faire ceci :
for $i ( 0 .. $#LoL ) {
print "\t elt $i is [ @{$LoL[$i]} ],\n";
}
ou peut-être même ceci. Remarquez la boucle intérieure.
for $i ( 0 .. $#LoL ) {
for $j ( 0 .. $#{$LoL[$i]} ) {
print "elt $i $j is $LoL[$i][$j]\n";
}
}
Comme vous pouvez le voir, cela devient un peu compliqué. C'est pourquoi il est parfois plus facile de prendre une variable temporaire en chemin :
for $i ( 0 .. $#LoL ) {
$aref = $LoL[$i];
for $j ( 0 .. $#{$aref} ) {
print "elt $i $j is $LoL[$i][$j]\n";
}
}
Hmm... c'est encore un peu laid. Pourquoi pas ceci :
for $i ( 0 .. $#LoL ) {
$aref = $LoL[$i];
$n = @$aref - 1;
for $j ( 0 .. $n ) {
print "elt $i $j is $LoL[$i][$j]\n";
}
}
Si vous voulez aller à une tranche (une partie d'une rangée) d'un tableau multidimensionnel, vous allez devoir faire un peu d'indiçage fantaisiste. C'est parce que, tandis que nous avons un joli synonyme pour les éléments seuls via la flèche de pointeur pour le déréférencement, il n'existe pas de telle commodité pour les tranches (souvenez-vous, bien sûr, que vous pouvez toujours écrire une boucle pour effectuer une opération sur une tranche).
Voici comment faire une opération en utilisant une boucle. Nous supposerons
avoir une variable @LoL comme précédemment.
@part = ();
$x = 4;
for ($y = 7; $y < 13; $y++) {
push @part, $LoL[$x][$y];
}
Cette même boucle pourrait être remplacée par une opération de tranche :
@part = @{ $LoL[4] } [ 7..12 ];
mais comme vous pouvez l'imaginer, c'est plutôt rude pour le lecteur.
Ah, mais et si vous vouliez une tranche à deux dimensions, telle qu'ayant $x dans 4..8 et $y
allant de 7 à 12 ? Hmm... voici la façon simple :
@newLoL = ();
for ($startx = $x = 4; $x <= 8; $x++) {
for ($starty = $y = 7; $y <= 12; $y++) {
$newLoL[$x - $startx][$y - $starty] = $LoL[$x][$y];
}
}
Nous pouvons réduire une partie du bouclage via des tranches
for ($x = 4; $x <= 8; $x++) {
push @newLoL, [ @{ $LoL[$x] } [ 7..12 ] ];
}
Si vous faisiez des Transformations Schwartziennes, vous auriez probablement choisi map pour cela
@newLoL = map { [ @{ $LoL[$_] } [ 7..12 ] ] } 4 .. 8;
Bien que si votre directeur vous accusait de rechercher la sécurité de l'emploi (ou l'insécurité rapide) à l'aide d'un code indéchiffrable, il serait difficile d'argumenter. :-) Si j'étais vous, je mettrais cela dans une fonction :
@newLoL = splice_2D( \@LoL, 4 => 8, 7 => 12 );
sub splice_2D {
my $lrr = shift; # ref à une liste de refs. de liste !
my ($x_lo, $x_hi,
$y_lo, $y_hi) = @_;
return map {
[ @{ $lrr->[$_] } [ $y_lo .. $y_hi ] ]
} $x_lo .. $x_hi;
}
perldata(1), perlref(1), perldsc(1)
Tom Christiansen tchrist@perl.com
Dernière mise à jour : Thu Jun 4 16:16:23 MDT 1998
Roland Trique (roncevaux@mail.dotcom.fr)