-+  Documenti
 |-  Bibliografia
 |-  Articoli
 |-  Perlfunc
 |-  F.A.Q.
 |-  F.A.Q. iclp
-+  Eventi
-+  Contatti
-+  Blog
-+  Link



 

Versione stampabile.


Michele Beltrame
Michele Beltrame vive a Maniago, PN, è programmatore Perl e convinto sostenitore del movimento Open Source. Pu√ò essere raggiunto via e-mail all'indirizzo arthas@perl.it

Data di pubblicazione: Questo articolo è stato pubblicato su Dev n¬į77 e in questo sito per autorizzazione espressa del "Gruppo Editoriale Infomedia" (http://www.infomedia.it).

© 2007 Michele Beltrame.

1. Introduzione 

2. Array 

3. Code e stack con gli array 

4. Splice 

5. Altre funzioni utili 

6. Hash 

7. Conclusioni 

Introduzione

Una delle prime cose di cui tener conto quando si inizia a progettare un programma √® l'organizzazione dei propri dati. Le variabili scalari possono andare bene per applicazioni molto semplici, ma quando ad esempio c'√® una lista di nomi da memorizzare √® necessario ricorrere a strutture pi√Ļ complesse. Perl mette anzitutto a disposizione, come praticamente tutti i linguaggi di programmazione, gli array, cio√® collezioni di valori a ciascuno dei quali √® assegnato un indice numerico che permette di leggerli o modificarli. Sono poi gi√† inclusi nel linguaggio, e questa √® una peculiarit√† del Perl, gli hash, conosciuti anche come dizionari o array associativi: questi sono sempre delle collezioni di valori a ciascuno dei quali √® assegnata per√≤ una stringa (chiamata chiave) anzich√© un numero incrementale. Vediamo ora nel dettaglio le due strutture.

 Array

Un array, chiamato a volte impropriamente lista, è un gruppo di valori scalari nel quale è mantenuto un indice sequenziale che permette al programmatore di memorizzare un valore in una determinata posizione e di leggerlo o modificarlo in un secondo momento. Poniamo di dover gestire nel nostro programma quattro nomi di persona; potremmo memorizzarli in altrettante variabili scalari:

$nome1 = 'Michele';
$nome2 = 'Elena';
$nome3 = 'Luca';
$nome4 = 'Elisa';

Questo sistema pu√≤ anche andare bene se i nomi non sono moltissimi, ma in realtà si rivela già poco elegante se ne stiamo trattando più di due. Un array permette una gestione molto più comoda del tutto, e pu√≤ essere inizializzato come segue:

@nomi = ('Michele', 'Elena', 'Luca', 'Elisa');

L'inizializzazione è un'operazione del tutto facoltativa, in quanto si possono direttamente inserire gli elementi nella posizione desiderata secondo la sintassi che vedremo tra poche righe. Se le stringhe prevedono solo caratteri alfanumerici è possibile anche utilizzare la cosiddetta quoted syntax:

@nomi = qw(Michele Elena Luca Elisa);

che permette di evitare di dover sempre scrivere gli apici. Questa soluzione non effettua tuttavia alcuna interpolazione, quindi se per inizializzare le stringhe utilizzate gli apici doppi poiché dovete inserire qualche carattere particolare o variabile, dovrete utilizzare qq in luogo di qw.

I due frammenti di codice appena visti creano un array di nome nomi con quattro elementi. L'identificatore che contraddistingue gli array è, diversamente dagli scalari al cui nome è anteposto il dollaro, la chiocciolina (@). A questo punto l'array pu√≤ essere gestito in molti modi: se ad esempio vogliamo stamparne tutto il contenuto possiamo scrivere:

print @nomi;

Questo causerà la stampa dei vari elementi, nell'ordine in cui erano stati inseriti, senza alcun separatore; nel nostro caso l'output sarà quindi:

MicheleElenaLucaElisa

La cosa più utile è naturalmente gestire i vari elementi singolarmente. Ci√≤ è molto semplice, in quanto ogni elemento pu√≤ essere trattato come uno scalare. Quindi funzionano benissimo le seguenti istruzioni:

print $nomi[0];
$nomi[2] = 'Daniele';

La prima riga stampa il primo elemento dell'array (in Perl, come in molti altri linguaggi, l'indice di partenza è 0 e non 1), mentre la seconda cambia il valore del terzo elemento da Luca a Daniele. Come si nota, quando si gestiscono i singoli elementi non si antepone più la chiocciolina al nome dell'array, ma il dollaro: questo è perfettamente coerente, in quanto come detto ogni elemento viene trattato come uno scalare. Le parentesi quadre permettono di specificare l'indice (cioè la posizione) in cui è memorizzato l'elemento a cui interessa accedere. √ą anche possibile accedere a porzioni di array:

print @nomi[1,3];
print @nomi[1..3];

La prima istruzione stampa solo gli elementi di indice 1 e 3 dell'array: si può specificare un numero arbitrario di elementi separati da virgola. La seconda istruzione permette invece di stampare un intervallo, in questo caso quello che va dall'elemento 1 all'elemento 3, estremi compresi. Utilizzando le sintassi sopra descritte si possono anche creare nuovi array composti da parti o da tutto l'array originale, come nei seguenti esempi:

@nomi2 = @nomi;       # Copia tutto @nomi in @nomi2
@nomi3 = @nomi[1,3];  # Copia gli elementi 1 e 3 in @nomi3
                      # (in cui diventano 0 e 1)
@nomi4 = @nomi[1..3]; # Copia gli elementi 1, 2 e 3 in @nomi4
                      # (in cui diventano 0, 1 e 2)

In molti casi è utile conoscere il numero di elementi di un array; è possibile procedere come segue:

print scalar(@nomi);

In contesto scalare una variabile di tipo array indica il numero di elementi in essa contenuti. La funzione print(), come si è visto prima, non cambia il contesto in scalare, e bisogna quindi ricorrere alla funzione scalar(). In altri ambiti il contesto viene automaticamente cambiato, come nel seguente esempio:

if (@nomi > 2) { print "L'array ha più di 2 elementi!"; }

Il frammento sopra riportato funziona correttamente poiché l'interprete si rende conto che il contesto dell'espressione if è scalare, e quindi modifica di conseguenza il significato di @nomi. Un'altra sintassi che permette di conoscere il numero di elementi ma che non risente di problemi di contesto è la seguente:

print $#nomi;

È tuttavia necessario prestare attenzione: questa sintassi in realtà restituisce l'indice dell'ultimo elemento e non il numero di elementi dell'array. Nel nostro caso scalar(@nomi) visualizza 4, mentre $#nomi visualizza 3, poiché gli array in Perl hanno indice iniziale 0: quindi scalar(@nomi) = $#nomi+1.

In Perl il ridimensionamento degli array avviene in maniera automatica: un array viene allungato o accorciato dinamicamente quando si inseriscono e tolgono degli elementi. Se ad esempio, prendendo sempre in considerazione l'array descritto sopra, diamo il comando:

$nomi[10] = 'Alessia';

viene automaticamente allocata memoria per tutti gli elementi fino all'undicesimo (quello cioè con indice 10). Allo stesso modo possiamo semplicemente accorciarne la dimensione con:

$#nomi = 2;

che imposta l'indice più alto a due, tagliando quindi fuori tutti gli elementi successivi.

 Code e stack con gli array

Esistono delle comode funzioni che permettono di gestire gli elementi collocati agli estremi (testa e coda) degli array, e che permettono quindi di realizzare facilmente strutture come code e stack (detti anche pile). La testa di un array è costituita dall'elemento di indice più basso (cioè 0); è possibile aggiungere un elemento alla testa con la seguente funzione:

unshift @nomi, 'Cristian';

Questo causa l'inserimento del nome Cristian nell'array: esso assume indice 0, e tutti gli altri elementi si trovano di conseguenza il loro indice incrementato di una unità. Per rimuovere un elemento dalla testa si pu√≤ utilizzare:

$primo = shift @nomi;

L'elemento rimosso è restituito e pu√≤ essere memorizzato in una variabile: ancora non abbiamo visto le funzioni (che analizzeremo in una delle prossime puntate), quindi il meccanismo di restituzione dei valori verrà spiegato in tale sede. Dopo l'operazione, eventuali altri elementi contenuti nell'array si trovano chiaramente il loro indice decrementato di una unità. La gestione della coda (elemento dell'array con indice più alto) segue principi identici. Il comando per inserire un elemento alla fine dell'array è:

push @nomi, 'Cristian';

mentre quello per rimuoverlo è:

$primo = pop @nomi;

Le funzioni push() e unshift() possono in realtà inserire più di un elemento in coda o in testa all'array: è sufficiente passare come secondo parametro alla funzione un altro array, ad esempio:

push @nomi, @nuovinomi;
unshift @nomi, @nuovinomi;

Vediamo brevemente come utilizzare questi comandi per implementare code e stack. Anzitutto, una coda è una struttura dati in cui viene inserito un elemento alla volta, ed uno viene estratto. Esse sono anche chiamate strutture FIFO (first in first out), in quanto il primo elemento inserito è il primo ad uscire: in poche parole, la coda restituisce gli elementi nello stesso ordine in cui sono stati inseriti (un esempio è costituito dalle automobili al casello autostradale: la prima che arriva è la prima a passare). Appare a questo punto ovvio che push() e shift() possono servire perfettamente al caso: la prima viene utilizzata per aggiungere un elemento in coda all'array, e la seconda per prelevarne uno dalla testa. Invertendo i concetti di testa e coda dell'array, è possibile implementare una coda anche utilizzando unshift() e pop() ma, per ragioni di efficienza dell'interprete Perl, è consigliato l'uso dei primi due.

Uno stack è invece una strutture LIFO (last in first out), cioè una struttura dati in cui il primo elemento ad uscire è l'ultimo che è entrato. L'esempio pratico che dà il nome alla struttura (la cui traduzione è, come detto sopra, pila) è appunto la pila dei piatti lavati in una cucina: l'ultimo piatto lavato va a finire in cima, ed è quindi il primo ad essere riutilizzato. L'implementazione è anche in questo caso piuttosto semplice, e prevede l'uso di push() per inserire gli elementi e di pop() per estrarli in ordine inverso a quello di inserimento.

 Splice

Una delle funzioni più potenti messe a disposizione dal Perl per la manipolazione degli array è splice(). Essa è paradossalmente anche la meno utilizzata, in quanto per l'uso comune sono più che sufficienti le quattro funzioni sopra descritte (push(), pop(), unshift(), shift()), che di fatto rappresentano dei "casi particolari" di splice(). Quest'ultima pu√≤ infatti fare le veci di tutte le precedenti: le equivalenze sono visibili in tabella 1.

FunzioneEquivalente splice()
push (@arr, @newarr);
splice (@arr, $#newarr+1, 0, @newarr);
$elem = pop (@arr);
$elem = splice (@arr, -1);
unshift (@arr, @newarr);
splice (@arr, 0, 0, @newarr);
$elem = shift (@arr);
$elem = splice (@arr, 0, 1);
$arr[$i] = $elem;
splice (@arr, $i, 1, $elem);

Di fatto splice() rimuove tutti oppure un certo numero di elementi da un array a partire da un certa posizione, rimpiazzandoli eventualmente con altri. Di seguito ne sono riportati alcuni possibili utilizzi:

# Rimuove gli elementi di indice da 2 a 4
splice (@nomi, 2, 2);

# Fa lo stesso, con sintassi diversa
# (parte dal fondo, ma il risultato è il medesimo)
splice (@nomi, 4, -2);

# Rimuove tutti gli elementi con indice > 1
splice (@nomi, 2);

# Rimuove gli ultimi 3 elementi
splice (@nomi, -3);

# Sostituisce gli elementi do indice da 2 a 4
# con un nuovo array
splice (@nomi, 2, 4, @nuovinomi);

In quest'ultimo caso non è necessario che @nuovinomi sia esattamente di tre elementi: se è più lungo o più corto l'array viene automaticamente ridimensionato.

 Altre funzioni utili

Analizziamo in sintesi altre due funzioni utili nella gestione degli array, reverse() e sort():

@invnomi = reverse @nomi;
@ordnomi = sort @nomi;

La prima inverte l'ordine degli elementi dell'array @nomi, memorizzando il risultato dell'operazione in @invnomi. La seconda salva invece in @ordnomi una versione ordinata di @nomi. L'ordinamento utilizzato di default è quello alfabetico, ma è possibile cambiarlo passando una propria funzione di ordinamento: la cosa verrà discussa più in dettaglio quando si parlerà di funzioni, per ora vediamo solo un esempio di come si pu√≤ ordinare un array contenente valori numerici:

@ordnumeri = sort {$a <=> $b} @numeri;

 Hash

Un hash, detto anche array associativo, è una struttura simile ad un array: al posto degli indici sono per√≤ utilizzate delle stringhe libere, dette chiavi, che permettono di leggere o scrivere il valore ad esse associato. L'inizializzazione, anche in questo caso del tutto facoltativa, avviene come segue:

%articolo = (
  'codice'      => 22,
  'nome'        => 'Cobra',
  'descrizione' => 'Coltello da collezione',
  'prezzo'      => 110000
);

L'identificatore degli hash è il segno di percentuale (%). Anche in questo caso, come per gli array, è possibile utilizzare la quoted syntax per assegnare i valori: sia virgole che frecce possono essere sostituite da spazi entro l'operatore qw. Di fatto la freccia (=>) ha per l'interprete lo stesso significato della virgola, ma il suo uso rende il listato più comprensibile. Abbiamo ora creato un hash di quattro elementi: due scalari a valore numerico (codice e prezzo) e due stringhe (nome e descrizione). La sintassi per la gestione degli elementi di un hash è simile a quella che si usa per gli array. Per visualizzare un valore si scrive ad esempio:

print $articolo{'codice'};

Per impostarlo invece:

$articolo{'codice'} = 15;

Si può anche stampare l'intero hash con:

print %articolo;

L'output sarà costituito da ciascuna chiave seguita dal rispettivo valore, senza alcun separatore.

La funzione reverse() inverte le chiavi con i valori, assumendo che questi ultimi siano unici. La funzione sort() non deve invece essere applicata, in quanto un hash è per definizione non ordinato ed il suo uso porterebbe a risultati indesiderati. √ą possibile ottenere un array contenente tutte le chiavi oppure tutti i valori di un hash, tramite le funzioni keys() e values():

@k = keys %articolo;
@v = values %articolo;

Con il tempo ci si renderà conto che gli hash sono utilissimi in molti casi, ed il fatto che il Perl già li fornisca a livello di linguaggio è una comodità non da poco.

Array ed hash possono anche essere combinati tra loro per creare array multidimensionali, hash multidimensionali, array di hash e hash di array: per questo scopo è per√≤ indispensabile comprendere l'uso delle reference, che verranno spiegate più avanti.

 Conclusioni

In questa lezione sono state analizzate le due strutture dati fondamentali del Perl, a partire dalle quali se ne possono costruire molte altre, a seconda delle proprie esigenze. Alcuni dei concetti esposti possono apparire un po' nebulosi; essi saranno tuttavia più chiari una volte studiate funzioni e reference. Il mese prossimo verrà approfondita la programmazione condizionale, accennata nella seconda lezione del nostro corso, e verrà illustrato l'uso delle strutture iterative.

  1. Ellen Siever, Stephen Spainhour, Nathan Patwardhan - "Perl in a Nutshell", O'Reilly & Associates, Gennaio 1999
  2. Larry Wall, Tom Christiansen, Randal L. Schwartz - "Programming Perl (2nd edition)", O'Reilly & Associates, Settembre 1996
  3. Jon Orwant, Jarkko Hietaniemi, John Macdonald - "Mastering algorithms with Perl", O'Reilly & Associates, Agosto 1999


Ti è piaciuto questo articolo? Iscriviti al feed!











Devo ricordare i dati personali?






D:
Annunci Google