NOME


NOME

perllol - Manipolare Array di Array in Perl


DESCRIZIONE

Dichiarazione ed Accesso ad Array di Array

La cosa più semplice da costruire è un array di array (a volte detto imprecisamente lista di liste). È piuttosto semplice da comprendere, e praticamente tutto quello che si applica qui sarà anche applicabile nel seguito a strutture dati più elaborate.

Un array di array è solo un buon vecchio array @AoA accessibile utilizzando due indici, come $AoA[3][2]. Ecco una possibile dichiarazione dell'array:

    # assegnazione al nostro array, un array di riferimenti ad array
    @AoA = (
        [ "fred", "barney" ],
        [ "george", "jane", "elroy" ],
        [ "homer", "marge", "bart" ],
    );
    print $AoA[2][2];
  bart

Dovete prestare molta attenzione al fatto che la parentesi esterna è tonda; questo dipende dal fatto che state effettuando un'assegnazione ad un un @array, per cui avete bisogno di questo tipo di parentesi. Se non voleste un @AoA, ma solo un riferimento ad esso, potreste fare qualcosa tipo:

    # assegna un riferimento ad un array di riferimenti ad array
    $rif_a_AdA = [
        [ "fred", "barney", "pebbles", "bambam", "dino", ],
        [ "homer", "bart", "marge", "maggie", ],
        [ "george", "jane", "elroy", "judy", ],
    ];
    print $rif_a_AdA->[2][2];

Osservate che il tipo delle parentesi esterne è cambiato da tondo a quadrato, per cui la nostra sintassi di accesso è variata. Questo è necessario per il fatto che, diversamente da C, in Perl non potete scambiare liberamente array e riferimenti ad essi. $rif_a_AdA è un riferimento ad un array, laddove @AoA è un array vero e proprio. Similmente, $AoA[2] non è un array, ma un riferimento ad array. Ci si può allora chiedere come sia possibile scrivere:

    $AoA[2][2]
    $rif_a_AdA->[2][2]

invece di essere costretti ad utilizzare:

    $AoA[2]->[2]
    $rif_a_AdA->[2]->[2]

Bene, questo dipende dalla regola che dice che, limitatamente a parentesi adiacenti (siano esse quadrate o graffe), siete liberi di omettere la freccia di dereferenziazione. Ma non potete farlo per la prima parentesi se il riferimento è contenuto in uno scalare, il che significa che $rif_a_AdA lo deve sempre avere.

Primi passi in autonomia

Fin qui tutto a posto per la dichiarazione di una struttura dati fissa, ma cosa fare se avete bisogno di aggiungere nuovi elementi al volo, o costruire la struttura da zero?

Prima di tutto vediamo come leggerla da file. È qualcosa di molto simile ad aggiungere una riga per volta. Assumeremo che sia presente un semplice file nel quale ciascuna linea è una riga della nostra struttura, e ciascuna parola un elemento. Se state provando a costruire un array @AoA che contenga tutto questo, ecco il modo corretto per farlo:

    while (<>) {
        @tmp = split;
        push @AoA, [ @tmp ];
    }

Potete anche caricare i dati da una funzione:

    for $i ( 1 .. 10 ) {
        $AoA[$i] = [ una_funzione($i) ];
    }

In alternativa, potreste utilizzare una variabile temporanea con il contenuto dell'array:

    for $i ( 1 .. 10 ) {
        @tmp = una_funzione($i);
        $AoA[$i] = [ @tmp ];
    }

È molto importante che vi assicuriate di utilizzare il costruttore di riferimento ad array []; quanto segue sarebbe totalmente sbagliato:

    $AoA[$i] = @tmp;

Come potete vedere, assegnare un array ad uno scalare (perché $AoA[$i] è uno scalare, giusto?) avrebbe l'unico effetto di contare gli elementi in @tmp, il che non è probabilmente quel che volete.

Se state utilizzando use strict, potreste aver bisogno di qualche dichiarazione addizionale per farlo contento:

    use strict;
    my(@AoA, @tmp);
    while (<>) {
        @tmp = split;
        push @AoA, [ @tmp ];
    }

È chiaro che non avete bisogno di avere un array temporaneo esplicito:

    while (<>) {
        push @AoA, [ split ];
    }

Non avete neanche bisogno di utilizzare push(). Fate semplicemente un'assegnazione se sapete già dove volete mettere i dati:

    my (@AoA, $i, $line);
    for $i ( 0 .. 10 ) {
        $line = <>;
        $AoA[$i] = [ split ' ', $line ];
    }

In questo caso basta anche:

    my (@AoA, $i);
    for $i ( 0 .. 10 ) {
        $AoA[$i] = [ split ' ', <> ];
    }

In generale, però, conviene essere cauti nell'utilizzare funzioni che potrebbero potenzialmente restituire liste in un contesto scalare, senza che questo risulti in quale modo esplicito. Per il lettore occasionale quanto segue sarebbe più comprensibile:

    my (@AoA, $i);
    for $i ( 0 .. 10 ) {
        $AoA[$i] = [ split ' ', scalar(<>) ];
    }

Se voleste avere una variabile $rif_a_AdA come riferimento ad un array, dovreste fare qualcosa tipo:

    while (<>) {
        push @$rif_a_AdA, [ split ];
    }

Ora sapete come aggiungere nuove righe. Che ne pensate di aggiungere nuove colonne? Se stiamo parlando di semplici matrici, è spesso più facile utilizzare una semplice assegnazione:

    for $x (1 .. 10) {
        for $y (1 .. 10) {
            $AoA[$x][$y] = func($x, $y);
        }
    }
    for $x ( 3, 7, 9 ) {
        $AoA[$x][20] += func2($x);
    }

Non importa se gli elementi sono già lì o no: perl sarà felice di crearli per voi, impostando tutti gli elementi in mezzo necessari a undef, come è giusto che sia.

Se voleste solamente aggiungere elementi ad una riga, dovreste fare qualcosa di un po' più articolato:

    # aggiungere nuove colonne ad una riga
    push @{ $AoA[0] }, "wilma", "betty";

Da notare che non abbiamo potuto dire semplicemente:

    push $AoA[0], "wilma", "betty";  # SBAGLIATO!

Questa espressione, infatti, non compilerebbe nemmeno. Com'è possibile? Perché il primo argomento di push() deve essere un array in carne ed ossa, non solo un riferimento.

Accesso ai dati e Stampa

È ora di stampare la vostra struttura dati. Come farlo? Se volete solo un elemento è banale:

    print $AoA[0][0];

Se volete stampare tutto, però, non potete scrivere semplicemente

    print @AoA;         # SBAGLIATO

perché ricevereste solo la lista dei riferimenti, e perl non effettuerà mai una dereferenziazione implicita per conto vostro. Dovete mettere su uno o due cicli per conto vostro, dunque; il codice che segue stampa l'intera struttura, utilizzando for() in stile shell per iterare sull'array più esterno.

    for $aref ( @AoA ) {
        print "\t [ @$aref ],\n";
    }

Se voleste tener traccia degli indici esplicitamente, potreste fare come segue:

    for $i ( 0 .. $#AoA ) {
        print "\t la riga $i contiene [ @{$AoA[$i]} ],\n";
    }

o anche così (notate il ciclo interno):

    for $i ( 0 .. $#AoA ) {
        for $j ( 0 .. $#{$AoA[$i]} ) {
            print "l'elemento in posizione $i $j vale $AoA[$i][$j]\n";
        }
    }

Ebbene sì, comincia a diventare un po' complicato. Questo è il motivo per cui a volte è più semplice (e leggibile) utilizzare qualche variabile temporanea lungo il percorso:

    for $i ( 0 .. $#AoA ) {
        $aref = $AoA[$i];
        for $j ( 0 .. $#{$aref} ) {
            print "l'elemento in posizione $i $j vale $AoA[$i][$j]\n";
        }
    }

Rimane ancora un po' bruttino. Che ne pensate di questo?

    for $i ( 0 .. $#AoA ) {
        $aref = $AoA[$i];
        $n = @$aref - 1;
        for $j ( 0 .. $n ) {
            print "l'elemento in posizione $i $j vale $AoA[$i][$j]\n";
        }
    }

Slice

Se volete accedere ad una slice (una porzione di una riga costituita da elementi in posizioni adiacenti) in un array multidimensionale avrete bisogno di utilizzare un po' di indicizzazione ed un po' di fantasia. Se infatti abbiamo a disposizione un sinonimo comodo per accedere ad un singolo elemento, utilizzando la freccia di puntamento per la dereferenziazione, non esiste nessuno strumento simile per le slice. (Ricordate, ovviamente, che potete sempre scrivere un ciclo per effettaure l'estrazione di una slice).

Ecco come effettuare un'operazione (estrazione dei dati, nel caso particolare) utilizzando un ciclo. Assumeremo che @AoA sia lo stesso definito in precedenza.

    @part = ();
    $x = 4;
    for ($y = 7; $y < 13; $y++) {
        push @part, $AoA[$x][$y];
    }

Tale ciclo può essere rimpiazzato da un'operazione effettuata su una slice opportuna:

    @part = @{ $AoA[4] } [ 7..12 ];

ma, come avete già indovinato, è piuttosto criptico per chi legge.

Che dovreste fare se voleste una slice bidimensionale, come prendere i dati con $x in 4..8 e $y in 7..12? Beh, la strada più semplice è la seguente:

    @newAoA = ();
    for ($startx = $x = 4; $x <= 8; $x++) {
       for ($starty = $y = 7; $y <= 12; $y++) {
           $newAoA[$x - $startx][$y - $starty] = $AoA[$x][$y];
       }
    }

Possiamo ridurre i cicli espliciti utilizzando le slice:

    for ($x = 4; $x <= 8; $x++) {
        push @newAoA, [ @{ $AoA[$x] } [ 7..12 ] ];
    }

Se avete dimestichezza con la Schwartzian Transform (Trasformazione di Schwartz, da Randal L. Schwartz, N.d.T.), avreste probabilmente scelto map:

    @newAoA = map { [ @{ $AoA[$_] } [ 7..12 ] ] } 4 .. 8;

Per quanto sarebbe difficile, in questo caso, controbattere il vostro capo, se vi accusasse di cercare la certezza di conservare il vostro posto di lavoro (o perderlo molto rapidamente) utilizzando codice illeggibile. Fossi in voi, lo incapsulerei all'interno di una funzione:

    @newAoA = splice_2D( \@AoA, 4 => 8, 7 => 12 );
    sub splice_2D {
        my $lrr = shift;        # rif. ad un array di rif. ad array!
        my ($x_lo, $x_hi,
            $y_lo, $y_hi) = @_;
        return map {
            [ @{ $lrr->[$_] } [ $y_lo .. $y_hi ] ]
        } $x_lo .. $x_hi;
    }


VEDERE ANCHE

perldata(1), perlref(1), perldsc(1)


AUTORE

Tom Christiansen <tchrist@perl.com>

Last update: Thu Jun 4 16:16:23 MDT 1998


TRADUZIONE

Versione

La versione su cui si basa questa traduzione è ottenibile con:

   perl -MPOD2::IT -e print_pod perllol

Per maggiori informazioni sul progetto di traduzione in italiano si veda http://pod2it.sourceforge.net/ .

Traduttore

Traduzione a cura di Flavio Poletti.

Revisore

Revisione a cura di dree.

 NOME