Sei sulla pagina 1di 44

Flavio Poletti

Puoi ripetere?
http://www.perl.it/documenti/articoli/2006/07/puoi-ripetere.html
Perl un linguaggio molto ridondante e trasversale, nel senso che mette a disposizione
moltissimi costrutti per ottenere lo stesso risultato finale. Questa caratteristica deriva da
un vincolo progettuale posto da Larry Wall - ma non parleremo di questo. Come per altri
costrutti, anche per l'iterazione stato dato libero sfogo alla fantasia.
Non tutti i costrutti di iterazione, per, sono equivalenti fra di loro: molti, al contrario, costituiscono
delle specializzazioni che si rivelano assai utili in contesti molto particolari. Anche questa una
conseguenza del vincolo di progetto posto da Wall: se devo andare da un angolo di una stanza a
quello opposto di solito percorro la diagonale (se possibile), non mi sposto adiacente alle pareti; in
questo caso, alcuni tipi di iterazione sono stati specializzati per consentire di risolvere determinati
problemi in maniera rapida e concisa.

while,

il sempreverde

Il primo costrutto di iterazione - quello per eccellenza, potremmo dire - while. Di una semplicit
disarmante:
ETICHETTA while (ESPRESSIONE) BLOCCO

ove:
ETICHETTA qualcosa di cui ci preoccuperemo pi tardi;
ESPRESSIONE una qualsiasi espressione che possa essere ricondotta ad un valore vero o
falso (quest'ultimo rappresentato dal numero 0 intero, dalla stringa '0', dalla stringa vuota,
dal valore undef o da una lista vuota);
BLOCCO qualcosa che comincia e finisce con delle parentesi graffe.
Il significato semplice: mentre l'ESPRESSIONE vera, viene eseguito il BLOCCO; quando diventa
falsa si procede oltre. Questo vuol dire che, se l'ESPRESSIONE falsa da subito, il BLOCCO non
viene eseguito. Quindi:
my $indice = 0;
while ($indice < 3) {
print("\$indice vale $indice\n");
++$indice;
}
print("basta!\n");

stampa:
$indice vale 0
$indice vale 1
Perl Mongers Italia. Tutti i diritti riservati.

1 of 44

Flavio Poletti

Puoi ripetere?
http://www.perl.it/documenti/articoli/2006/07/puoi-ripetere.html
$indice vale 2
basta!

mentre;
my $indice = 3;
while ($indice < 3) {
print("\$indice vale $indice\n");
++$indice;
}
print("basta!\n");

stampa solo basta!, perch la condizione falsa sin dall'inizio.


Occorre prestare attenzione al fatto che a volte potreste vedere una lista dove, in realt... non c'!
Normalmente, infatti, la "virgola" un operatore vero e proprio che serve a costruire liste:
my $porzione = substr 'ciao_belli', 0, 4;

In questo caso, la sequenza dei parametri dati a substr:


'ciao_belli', 0, 4

una lista, e l'operatore "virgola" serve a separare i vari elementi della stessa. proprio cos, per
costruire una lista non si usano le parentesi, ma le virgole! L'uso delle parentesi a volte
necessario per risolvere la precedenza degli operatori, come nel seguente esempio:
my @unico_elemento = 'ciao', 'a', 'tutti';

In questo caso, infatti, il segno di uguale dell'assegnazione ha una precedenza maggiore


dell'operatore virgola, quindi come se avessimo scritto:
((my (@unico_elemento) = 'ciao'), 'a', 'tutti');

Una sorpresa pi insidiosa la troviamo con il while ( d'ora innanzi metteremo l'output
immediatamente dopo il programma, separati da __END__):
my $indice = 1;
while (1, 1, $indice) {
print("\$indice vale $indice\n");
$indice = 0;
}
print("basta!\n");
__END__
$indice vale 1
basta!
Perl Mongers Italia. Tutti i diritti riservati.

2 of 44

Flavio Poletti

Puoi ripetere?
http://www.perl.it/documenti/articoli/2006/07/puoi-ripetere.html
Qui abbiamo una falsa lista di tre elementi: in realt si tratta di un'espressione composita, che
valutata in contesto booleano (come nel caso di while) si riduce al valore dell'ultima sottoespressione. Nel caso considerato, infatti, quando $indice posto a zero il ciclo termina.
Diverso il caso in cui si utilizzi un array:
my @array = ( 1, 0, 1 );
while ( @array ) {
print("\@array vale ( @array )\n");
pop @array;
}
print("basta array!\n");
__END__
@array vale ( 1 0 1 )
@array vale ( 1 0 )
@array vale ( 1 )
basta array!

Da notare che i tre valori (1, 0 ed 1) sono inseriti, questa volta, in una lista: le parentesi
"proteggono" infatti le due virgole, costruttrici di lista. Qui il ciclo non si arresta quando l'ultimo
elemento nullo, ma solo quando l'array diventa vuoto. Questo accade perch gli array, valutati in
contesto booleano/scalare, restituiscono il numero di elementi presenti, ed una espressione
booleana un caso particolare di valutazione in un contesto scalare.

Un po' di magia Perl


Come detto, Wall ha voluto creare un linguaggio che consentisse di prendere le scorciatoie, per
quanto diagonali fossero. Lo scopo scrivere il meno possibile le cose che si fanno pi spesso.
Poich uno dei retroscena pi classici di Perl l'analisi di testi o di file in generale, allora ovvio
che la lettura dei dati sia stata resa il pi semplice possibile. while non rimasto immune da
questo processo, ed il seguente costrutto lo dimostra:
while (<>) {
s/ciao/CIAO/g;
print;
}

un semplice filtro, che cambia tutti i ciao in ingresso in CIAO. Il problema : su cosa stanno
lavorando l'operatore di sostutuzione e la funzione print? Il manuale ci d una mano: in
mancanza di specifica, questi due lavorano sulla variabile jolly $_. Bene, obietterete: chi l'ha mai
inizializzata? La risposta semplice: ci ha pensato while.

Perl Mongers Italia. Tutti i diritti riservati.

3 of 44

Flavio Poletti

Puoi ripetere?
http://www.perl.it/documenti/articoli/2006/07/puoi-ripetere.html
Ogni volta che in Perl scrivete qualcosa tipo:
while (<HANDLE>) { # Puo` anche essere lessicale, come in <$handle>
# Fai qualcosa con $_
}

in realt viene letto pi o meno come segue:


while (defined($_ = <HANDLE>)) { # o <$handle>, come sopra
# Fai qualcosa con $_
}

Ossia, in presenza di quel particolare modo di scrivere ESPRESSIONE, Perl prende l'iniziativa e:
ad ogni iterazione, prende una riga di ingresso (in accordo con il valore della variabile $/,
ma questa un'altra storia) e la assegna a $_;
verifica se il valore di $_ definito, come prova che effettivamente si sia letto qualcosa ed il
file non sia finito.
Attenzione: perch il trucco funzioni, ESPRESSIONE deve essere proprio come abbiamo scritto
sopra. Se volete aggiungere altri test, come ad esempio:
while ($pippo > 1 && <>) {
# Peccato! non vale piu`!
}

non avrete pi il comportamento magico.

Vita reale: iterare su una hash


Avete una hash e dovete visitarla tutta, sia chiavi che valori. Che fate? Presto!
Arrivati a questo punto, avete poco da scegliere. Conoscete solo while! Ma come usarlo?
Semplice, con l'aiuto di each:
my %hash = (a => 'b', c => 'd', e => 'effe');
while (my ($chiave, $valore) = each(%hash)) {
print "chiave: $chiave; valore: $valore\n";
}
print("fine hash\n");
__END__
chiave: e; valore: effe
chiave: c; valore: d
chiave: a; valore: b
fine hash
Perl Mongers Italia. Tutti i diritti riservati.

4 of 44

Flavio Poletti

Puoi ripetere?
http://www.perl.it/documenti/articoli/2006/07/puoi-ripetere.html
Questo ci insegna due cose:
1. each mantiene un proprio stato interno per poter iterare sull'hash (non potrebbe essere
altrimenti). Questo stato condiviso da TUTTO il programma, e per quanto ce ne sia uno
distinto per ciascuna hash, occorre prestare molta attenzione, perch chiamate a keys e
values, ad esempio, resettano questo iteratore interno;
2. non si pu mai contare sull'ordine in cui gli elementi sono disposti in una hash. Questi sono
disposti secondo un algoritmo che cerca di minimizzare gli accessi necessari per trovare un
dato elemento; fare assunzioni sul fatto di trovare un particolare ordine equivale a chiedere
guai.
In generale, fossi in voi eviterei ed aspetterei di sapere qualcosa di pi su for e foreach.

Il fratellino bastian contrario


Se al posto di while mettete until, il senso dell'ESPRESSIONE booleana viene invertito, ossia si
prosegue se ESPRESSIONE falsa, e si esce dal ciclo se vera. In tutto e per tutto i seguenti
costrutti coincidono:
ETICHETTA until ( ESPRESSIONE) BLOCCO
ETICHETTA while (! ESPRESSIONE) BLOCCO

Le radici, inutile dirlo, sono linguistiche; se sia un bene o un male avere tutte queste varianti
potete deciderlo solo voi.

I gemelli del giro: for e foreach


In italiano esistono pochi sinonimi in senso stretto, ossia parole che hanno lo stesso, identico
significato. "tra" e "fra" sono sinonimi a tutti gli effetti; "bello" e "prestante" si avvicinano, ma
vogliono significare due cose leggermente differenti.
Perl, viste le sue radici linguistiche, non fa eccezione. I costrutti di iterazione sono sinonimi in
senso ampio: tutti inducono un significato di ripetizione, ognuno per con una sua cifra peculiare.
Beh, anche Perl ha i suoi "tra" e "fra": sono for e foreach. Per quanto voi, nella vostra testa,
possiate tendere ad assegnare significati differenti (ed in inglese lo hanno), per Perl sono la stessa,
identica cosa. Provare per credere.
Se siete pigri, dunque, scrivete sempre for. Se amate un po' pi la leggibilit, probabilmente in
certi casi sarete pi portati a scrivere foreach, ma ricordate: tutto nella vostra testa (ed in
Perl Mongers Italia. Tutti i diritti riservati.

5 of 44

Flavio Poletti

Puoi ripetere?
http://www.perl.it/documenti/articoli/2006/07/puoi-ripetere.html
quella di chi legge il vostro codice).
La cosa bella che queste due parole chiave condividono due significati distinti. Per essere pi
chiari, for e foreach possono essere utilizzati mediante due stili di chiamata differenti
(utilizzeremo nel seguito la convenzione della migliore leggibilit, ma sono identici!):
ETICHETTA for (ESPRESSIONE1; ESPRESSIONE2; ESPRESSIONE3) BLOCCO
ETICHETTA foreach VAR (LISTA) BLOCCO

Il primo un figlio diretto del C, che ha inventato il ciclo for. Stesse modalit di utilizzo:
ESPRESSIONE1 un'inizializzazione, sempre eseguita; ESPRESSIONE2 viene valutata in un
contesto booleano, e decide se si va avanti o si scherzato; BLOCCO viene eseguito ogni volta che
ESPRESSIONE2 d il permesso; ESPRESSIONE3 viene eseguita subito dopo BLOCCO e prima di
una nuova valutazione di ESPRESSIONE2. Ad esempio:
for (my $indice = 0; $indice < 3; ++$indice) {
print("\$indice vale $indice\n");
}
print("basta!\n");
__END__
$indice vale 0
$indice vale 1
$indice vale 2
basta!

un modo pi conciso (ed elegante, e leggibile) di iterare su un indice rispetto a quanto fatto in
precedenza con while. Non che while non serva egregiamente allo scopo: solo che per dire
questa cosa in particolare meglio utilizzare for, cos come nel linguaggio naturale utilizzate la
parola pi adatta alle circostanze, se la sapete, altrimenti ripiegate su qualcosa di meno preciso ma
che serve allo scopo.
Nell'altra modalit, la variabile in VAR viene di volta in volta resa un alias per il successivo
elemento della LISTA, quindi operazioni in BLOCCO su di essa modificano l'elemento
corrispondente nella LISTA stessa. Ebbene s, se utilizzate un array nella LISTA potete modificarne
gli elementi (ossia, il valore di tali elementi, ma non quanti sono: il numero di elementi della lista
rimarr invariato). Se la variabile non viene specificata... come al solito a lavorare una versione
localizzata di $_. Proseguendo con un esempio:
foreach my $indice ( 0 .. 2 ) {
print("\$indice vale $indice\n");
}
print("basta!\n");

Perl Mongers Italia. Tutti i diritti riservati.

6 of 44

Flavio Poletti

Puoi ripetere?
http://www.perl.it/documenti/articoli/2006/07/puoi-ripetere.html
__END__
$indice vale 0
$indice vale 1
$indice vale 2
basta!

Gioie e dolori dell'aliasing


L'utilizzo dell'aliasing nasce da due esigenze fondamentali, una legata all'efficienza (evita di fare
una copia del valore), l'altra legata invece alla possibilit di fare proprio questo tipo di operazioni in
situ.
for/foreach, dunque, si possono utilizzare per modificare i valori di un array "sul posto":
my @array = (1, 2, 3);
print("prima: ( @array )\n");
foreach my $alias (@array) {
$alias *= 2; # Opera una trasformazione su $alias e, dunque, su @array
}
print("dopo : ( @array )\n");
__END__
prima: ( 1 2 3 )
dopo : ( 2 4 6 )

Bello, eh? Come ogni cosa potente, fate bene attenzione a come la utilizzate, perch potrebbe
riverlarsi un'arma a doppio taglio e rovinarvi gli array (oltre alla giornata, ovviamente).
C' per un altro modo con cui potete soffrire i dolori dell'aliasing, ed presumendo che tali effetti
siano visibili anche al di fuori del ciclo. Cosa pensate che stampi il brano di codice che segue?
my @array = (1, 2, 3);
print("prima: ( @array )\n");
my $alias = 7; # Nota: dichiarato prima!!!
foreach $alias (@array) {
$alias *= 2; # Opera una trasformazione su $alias e, dunque, su @array
}
print("dopo : ( @array )\n");
print("alias: $alias\n");

Ma semplice, direte! Stampa esattamente quello che stampava l'altro script di prima, solo che
aggiunge una riga in cui stampa:
alias: 6

alla fine! Sbagliato:


Perl Mongers Italia. Tutti i diritti riservati.

7 of 44

Flavio Poletti

Puoi ripetere?
http://www.perl.it/documenti/articoli/2006/07/puoi-ripetere.html
prima: ( 1 2 3 )
dopo : ( 2 4 6 )
alias: 7

Che succede? Niente di particolare: solo che $alias, al di fuori del ciclo, torna ad essere il buon
$alias della porta accanto, privo della sua doppia identit, riacquistando quindi il valore che
aveva prima di diventare un agente segreto di sua maest il ciclo foreach: il valore 7, in poche
parole (anzi, in poche cifre). Questo vi insegni dunque ad evitare di dichiarare la variabile di alias al
di fuori del ciclo: potreste avere brutte sorprese. Preferite invece sempre la dichiarazione
direttamente nel ciclo, come in:
foreach my $alias (...

Programmatore avvisato mezzo debuggato...

Vita reale: iterare su un array


Normalmente si desidera iterare sui valori di un array, per cui sufficiente utilizzare il ciclo che
segue:
my @array = qw( uno due tre quattro cinque );
foreach (@array) {
print("numero: $_\n");
}
print("numeri finiti\n");
__END__
numero: uno
numero: due
numero: tre
numero: quattro
numero: cinque
numeri finiti

A volte, invece, necessario iterare su un indice che punta nell'array; in questo caso si pu fare
cos:
my @array = qw( uno due tre quattro cinque );
foreach (0 .. $#array) { # $#array contiene l'ultimo indice utile in @array
print("numero: $array[$_]\n");
}
print("numeri finiti\n");
__END__
numero: uno
numero: due
numero: tre
Perl Mongers Italia. Tutti i diritti riservati.

8 of 44

Flavio Poletti

Puoi ripetere?
http://www.perl.it/documenti/articoli/2006/07/puoi-ripetere.html
numero: quattro
numero: cinque
numeri finiti

Vita reale: iterare su una hash


Ricordate quando prima abbiamo costruito un ciclo di visita di una hash utilizzando while ed
each? Eravamo rimasti con un consiglio: cercate di evitare quell'approccio, a meno che l'ambiente
in cui lo utilizzate sia veramente controllato.
L'approccio pi robusto a mio modesto avviso il seguente:
my %hash = (a => 'b', c => 'd', e => 'effe');
foreach (keys %hash) {
print("chiave: $_; valore: $hash{$_}\n");
}
print("fine hash\n");
__END__
chiave: e; valore: effe
chiave: c; valore: d
chiave: a; valore: b
fine hash

La funzione keys restituisce la lista delle chiavi dell'hash, e questa operazione viene fatta prima di
iniziare il ciclo foreach vero e proprio. Come conseguenza, non c' nessuno stato nascosto da
qualche parte che possa essere rovinato per sbaglio. Ovviamente tutto si paga: se l'hash
particolarmente ampia, questa operazione di estrazione della lista delle chiavi onerosa dal punto
di vista del tempo e della memoria richiesti. Non esistono pranzi gratis.
Questo approccio ha per un altro vantaggio: vi d la possibilit di accedere l'hash con un ordine
sulle chiavi. Basta utilizzare sort:
my %hash = (a => 'b', c => 'd', e => 'effe');
foreach (sort keys %hash) { # Ora le chiavi sono ordinate
print("chiave: $_; valore: $hash{$_}\n");
}
print("fine hash\n");
__END__
chiave: a; valore: b
chiave: c; valore: d
chiave: e; valore: effe
fine hash

Perl Mongers Italia. Tutti i diritti riservati.

9 of 44

Flavio Poletti

Puoi ripetere?
http://www.perl.it/documenti/articoli/2006/07/puoi-ripetere.html

Controllori di ciclo
Quanti di voi conoscono C? Uhmmm, vedo poche mani alzate e mi chiedo se sia un bene o un
male. Beh, quelli che lo conoscono saranno lieti di sapere che esistono modificatori di ciclo
analoghi a break e continue. Per gli altri, sufficiente andare avanti.
I cicli visti consentono in generale di essere alterati in maniera immediata dall'interno del BLOCCO
che viene eseguito; questo grazie a tre parole chiave:
last indica che occorre uscire immediatamente dal BLOCCO e terminare il ciclo (corrisponde
al break del C);
next indica che occorre uscire immediatamente dal BLOCCO e verificare le condizioni per
accedere, eventualmente, all'iterazione successiva (corrisponde al continue del C);
redo indica che occorre re-iniziare l'esecuzione del BLOCCO, senza passare per le
espressioni di controllo del ciclo ( specifico di Perl).
Vediamo alcuni esempi per fissare le idee.
Nel primo, creaiamo un semplice filtro, dove vengono eliminate tutte le righe di commento puro
dallo standard input. Utilizziamo un ciclo while avvalendoci del caso particolare (lettura di un
filehandle) descritto in precedenza. Tutte le righe che cominciano con zero o pi spazi, seguite da
un cancelletto, sono righe di commento pure e pertanto innescano la chiamata a next, saltando il
comando print successivo.
while (<>) {
next if /^\s*#/; # Vai alla prossima iterazione se e` un commento
print;
# altrimenti, stampa la riga
}

Nel secondo esempio, invece, vediamo le potenzialit di last. Supponiamo di scandire un testo in
ingresso, e di volerci fermare quando compare la parola STOP nella riga:
while (<>) {
last if /STOP/;
print;
}

L'uso di redo piuttosto raro, ma citando dal manuale " usualmente utilizzato da programmi che
vogliono mentire a se stessi su quel che stato appena immesso". Azzardiamo un esempio,
stampando le righe di un file "immerso" con __DATA__ e raddoppiando tutte le righe che
contengono la parola RADDOPPIAMI, immettendo il numero della riga letta:
Perl Mongers Italia. Tutti i diritti riservati.

10 of 44

Flavio Poletti

Puoi ripetere?
http://www.perl.it/documenti/articoli/2006/07/puoi-ripetere.html
#!/usr/bin/perl
use strict;
use warnings;
while (<DATA>) {
print("$. $_");

# Itera sulle righe dopo __DATA__


# Stampa numero di riga letta e la riga stessa

# Ripeti il blocco se la sostituzione va a buon fine. Questo ha anche


# il compito di eliminare la stringa RADDOPPIAMI, in modo che redo
# non inizi un ciclo infinito!
redo if s/RADDOPPIAMI\s+//;
}
__DATA__
Ciao a tutti
RADDOPPIAMI caro!
Ariciao
a
tutti

stampa
1
2
2
3
4
5

Ciao a tutti
RADDOPPIAMI caro!
caro!
Ariciao
a
tutti

Come potete vedere la riga "2" ripetuta, ma lavora su un input differente grazie alla modifica
fatta con l'operatore di sostituzione "s". Non granch utile come script, eh?
Ok, ad onor del vero quando ho iniziato a scrivere questo articolo l'utilizzo di redo era abbastanza
raro. Ora che uscito il libro "Perl Best Practices" di Damian Conway, per, ci si pu aspettare che
diventi di moda. Conway suggerisce di utilizzare l'accoppiata for e redo laddove si debba
eseguire un ciclo su un numero grosso modo fissato di iterazioni, con qualche sporadica deviazione
(ad esempio legata a qualche input anomalo dall'utente). Personalmente non sono molto convinto,
ma di nuovo a voi la scelta!
Una delle peculiarit dei controllori di ciclo che finalmente ci svelano il mistero di quelle
benedette ETICHETTE. Ciascuno di essi, infatti, pu essere riferito ad una etichetta particolare, il
che permette di fare cose belle e pericolose:
ESTERNO:
while (<>) {
my @campi = split /:/; # Dividi l'ingresso in campi separati da :
my $conteggio = 0;
INTERNO: foreach my $v (@campi) {
$conteggio += $v;
next ESTERNO if $conteggio > 10;
}
Perl Mongers Italia. Tutti i diritti riservati.

11 of 44

Flavio Poletti

Puoi ripetere?
http://www.perl.it/documenti/articoli/2006/07/puoi-ripetere.html
print("conteggio: $conteggio\n");
}

Non dimenticate che le etichette sono sempre terminate da un carattere ":"! Nel caso di questo
esempio, entrambi i cicli sono stati etichettati, anche se in realt stata utilizzata solo l'etichetta
ESTERNO. Come si pu vedere, il ciclo ESTERNO legge riga per riga dall'input, lo divide in campi in
base al carattere separatore ":" ed inizializza una variabile di conteggio.
Il ciclo interno itera sull'array risultante dalla split e somma i vari elementi fra di loro. Se per
tale somma risulta maggiore di 10, si passa direttamente alla riga di input successiva,
interrompendo il foreach e saltando la print. Niente che non si potesse fare altrimenti, con un
opportuno utilizzo di variabili di stato; in questo caso, per, la soluzione risulta particolarmente
concisa, leggibile e meno soggetta ad errori.
Riassumendo, se l'uso di questo tipo di scorciatoie aumenti o meno leggibilit e manutenibilit va
valutato caso per caso - il consiglio comunque di non abusarne!

Un ciclo che non sembra tale


Un aspetto molto interessante dei controllori di ciclo next, last e redo consiste nel fatto che
agiscono anche a livello di blocco nudo. Per questo motivo, in teoria potremmo risparmiarci
completamente le parole chiave per il ciclo!
my $indice = 0;
{ # Attenzione: si apre un blocco
print("\$indice vale $indice\n");
redo if ++$indice < 3;
} # Chiusura del blocco
print("finito!\n");
__END__
$indice vale 0
$indice vale 1
$indice vale 2
finito!

Si pu intuire come redo giochi un ruolo cruciale nel trasformare il blocco in un ciclo, mentre
next pressoch privo di senso, dal momento che ha l'effetto di terminare il blocco all'istante
come last (risultando per meno leggibile).
Una curiosit: quanto detto non funziona in generale con i blocchi associati ad if e unless. Se
volete utilizzare next e compagni in queste condizioni, baster inserire un blocco interno
raddoppiando le parentesi:
Perl Mongers Italia. Tutti i diritti riservati.

12 of 44

Flavio Poletti

Puoi ripetere?
http://www.perl.it/documenti/articoli/2006/07/puoi-ripetere.html
foreach my $value ( 1 .. 45 ) {
if ($value % 2) {{ # Doppie parentesi aperte!
last if $value % 3; # esci subito dal blocco
next if $value % 5; # anche qui, ma si legge peggio
print("abbiamo un candidato: $value\n");
}} # Doppie ne ho aperte, doppie ne chiudo
}
__END__
abbiamo un candidato: 15
abbiamo un candidato: 45

Si noti come last e next lavorano all'interno del blocco immerso dentro l'if, e non sul foreach.

continue

pure

Tutti i tipi di cicli visti fino ad ora, con l'eccezione del for in stile C, possono assumere anche la
forma estesa mediante la parola chiave continue:
ETICHETTA
ETICHETTA
ETICHETTA
ETICHETTA

while (ESPRESSIONE) BLOCCO continue BLOCCO2


until (ESPRESSIONE) BLOCCO continue BLOCCO2
foreach VAR (LISTA) BLOCCO continue BLOCCO2
BLOCCO continue BLOCCO

Il BLOCCO2 viene eseguito indistintamente a valle dell'uscita (eventualmente prematura) da


BLOCCO, prima della valutazione dell'espressione booleana o di incremento (se presenti). Tale
opzione molto comoda se si stanno utilizzando next e last, poich questi controllori di flusso
interrompono BLOCCO all'istante.
Confrontiamo:
my $contatore = 0;
while (<DATA>) {
next if /SALTAMI/;
print "$contatore: $_";
++$contatore;
}
__DATA__
Ciao
a
SALTAMI
tutti
quanti

con
my $contatore = 0;
while (<DATA>) {
Perl Mongers Italia. Tutti i diritti riservati.

13 of 44

Flavio Poletti

Puoi ripetere?
http://www.perl.it/documenti/articoli/2006/07/puoi-ripetere.html
next if /SALTAMI/;
print "$contatore: $_";
}
continue {
++$contatore;
}
__DATA__
Ciao
a
SALTAMI
tutti
quanti

Nel primo caso, $contatore conta solamente le righe che non contengono la parola SALTAMI: se
l'espressione regolare /SALTAMI/ verificata, infatti, next interrompe l'iterazione corrente e
passa alla successiva, saltando sia la stampa che l'incremento. Il risultato finale :
0:
1:
2:
3:

Ciao
a
tutti
quanti

Nel secondo caso, invece, $contatore conta tutte le righe in ingresso, non solo quelle stampate.
Il blocco successivo a continue, infatti, viene eseguito sempre, a prescindere dal fatto che next
intervenga o meno. Abbiamo dunque:
0:
1:
3:
4:

Ciao
a
tutti
quanti

Il contatore stato evidentemente incrementato (si passa da 1 a 3).


Si faccia attenzione al fatto che redo fa ripartire il blocco corrente a prescindere dal ciclo in cui si
trova. Questo vuol anche dire che continue non viene eseguita quando si chiama redo.

Un altro po' di linguistica: forme suffisse


Abbiamo gi discusso in precedenza sulle radici linguistiche di Perl. Tali radici hanno attecchito per
dar vita ad altri modi per impostare dei cicli: le forme suffisse. Vediamo di che si tratta.
Supponiamo che vogliate stampare i valori da 1 a 10, uno per riga. Nonostante esistano molti modi
per farlo, il pi semplice probabilmente quello che segue:
foreach (1 .. 10) {
print "$_\n";
}
Perl Mongers Italia. Tutti i diritti riservati.

14 of 44

Flavio Poletti

Puoi ripetere?
http://www.perl.it/documenti/articoli/2006/07/puoi-ripetere.html
In linguaggio naturale, suonerebbe qualcosa tipo "per ogni valore da 1 a 10, stampa il valore e vai
a capo". Si noti che qui si d l'accento sull'intervallo dei valori (per ogni valore da 1 a 10),
relegando l'operazione da compiere in un piano leggermente pi retrostante.
Se volessimo porre l'accento sull'operazione da compiere, invece, probabilmente diremmo "stampa
e vai a capo, per ogni valore da 1 a 10". Bene, Perl ci consente di farlo:
print "$_\n" foreach 1 .. 10;

Il ciclo divenuto un modificatore dell'istruzione di stampa, che rimane detentrice di un ruolo


centrale nella frase. Questo tipo di approccio l'abbiamo gi visto in precedenza con if:
my $contatore = 0;
while (<DATA>) {
next if /SALTAMI/;
print "$contatore: $_";
++$contatore;
}
__DATA__
Ciao
a
SALTAMI
tutti
quanti

# if come suffisso

Le possibili forme suffisse per le iterazioni sono tre:


ISTRUZIONE while ESPRESSIONE;
ISTRUZIONE until ESPRESSIONE;
ISTRUZIONE foreach LISTA;

# Il sempreverde
# Il solito bastian contrario
# Anche for va bene, ovviamente

Si noti che in questo caso non necessario mettere ESPRESSIONE o LISTA fra parentesi. Va inoltre
ricordata una cosa molto importante: la forma suffissa una forma linguistica, che consente di
porre maggiore accento su quello che si fa piuttosto che su quello che si itera. Il fatto che la
sezione di controllo del ciclo venga dopo, comunque, non vuol dire che il controllo stesso venga
effettuato dopo. In poche parole, anche se compaiono dopo ISTRUZIONE, sia i controlli di
while/until che l'avanzamento lungo LISTA di foreach avvengono sempre prima di essa.

Prima mena, poi discuti


Abbiamo visto che i controlli di ciclo suffissi sono, alla fine della fiera, solamente degli accorgimenti
di natura linguistica. Che dobbiamo fare per avere un ciclo che esegua i controlli non all'inizio, ma
alla fine di ciascun gruppo di istruzioni? Dobbiamo utilizzare una delle tante facce di do:
# Prima mena, poi discuti :)
my $contatore = 3;
Perl Mongers Italia. Tutti i diritti riservati.

15 of 44

Flavio Poletti

Puoi ripetere?
http://www.perl.it/documenti/articoli/2006/07/puoi-ripetere.html
do {
print "\$contatore vale $contatore\n";
} while $contatore < 3;
print "ok, basta\n";
__END__
$contatore vale 3
ok, basta

Analogo discorso vale, ovviamente, per until. Tanto per levarci la curiosit, possiamo usare
next, last e redo? In realt... no, perch quello creato da do non un blocco nel senso
tradizionale del termine. Nel manuale perlsyn <http://perldoc.perl.org/perlsyn.html> trovate cosa
fare per poter utilizzare i vostri (possibilmente infidi) nuovi amici.

sort, grep e map: gli specialisti del ciclo


Fino ad ora abbiamo analizzato i costrutti di iterazione basilari di Perl; nonostante sia virtualmente
possibile utilizzare sempre un ciclo while scritto opportunamente, la variet lessicale di Perl ci
consente di utilizzare il costrutto che, di volta in volta, meglio si adatta al concetto che vogliamo
esprimere (ad esempio utilizzando una forma suffissa).
Come abbiamo detto in apertura, per, Perl cerca anche di rendere particolarmente semplici le
operazioni pi di routine. Tale semplificazione viene attuata in modo sottile: da una parte viene
specializzato un processo di iterazione, dall'altra viene generalizzato dando all'utente la possibilit
di intervenire sul cuore della funzione.
Tutte e tre le funzioni lavorano come filtri, ossia producono liste a partire da altre liste. Questo
per non deve trarre in inganno o far abbassare la guardia: saremo in grado di fare danni anche
sui dati di partenza, state tranquilli!

Metti a posto, Battista!


La prima funzione di specializzazione/generalizzazione che vediamo serve a svolgere un'operazione
che spesso risulta necessaria: l'ordinamento di una lista di elementi. Tale operazione presuppone
un certo numero di cicli, durante i quali i vari elementi della lista sono confrontati fra di loro per
stabilire chi debba avere la precedenza.
Ci sono tre modi per utilizzarla:
sort LISTA
sort BLOCCO LISTA
sort FUNZIONE LISTA
Perl Mongers Italia. Tutti i diritti riservati.

16 of 44

Flavio Poletti

Puoi ripetere?
http://www.perl.it/documenti/articoli/2006/07/puoi-ripetere.html
Come al solito, molte cose possono essere date per scontate, per cui la prima modalit consente di
scrivere:
#!/usr/bin/perl
use strict;
use warnings;
my @disordinato = qw( eliseo ambrogio levino egis );
my @ordinato = sort @disordinato;
print "[ ", (join(", ", @ordinato)), " ]\n";
__END__
[ ambrogio, egis, eliseo, levino ]

Come sempre, per, il default potrebbe trarre in inganno, come dimostra il seguente esempio:
#!/usr/bin/perl
use strict;
use warnings;
my @disordinato = qw( 5 41 31 2 11 3 19 13 7 17 29 37 23);
my @ordinato = sort @disordinato;
print "[ ", (join(", ", @ordinato)), " ]\n";
__END__
[ 11, 13, 17, 19, 2, 23, 29, 3, 31, 37, 41, 5, 7 ]

Viste le radici linguistiche del Perl, non deve stupire che l'ordine assunto sia quello lessicografico,
che un modo snob di dire alfabetico. Per avere l'ordine numerico dobbiamo indicarlo
esplicitamente utilizzando la seconda opzione:
#!/usr/bin/perl
use strict;
use warnings;
my @disordinato = qw( 5 41 31 2 11 3 19 13 7 17 29 37 23);
my @ordinato = sort {
if ($a > $b)
{ 1 }
elsif ($a < $b) { -1 }
else
{ 0 }
} @disordinato;
print "[ ", (join(", ", @ordinato)), " ]\n";
__END__
[ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41 ]

Il BLOCCO ha queste caratteristiche:

Perl Mongers Italia. Tutti i diritti riservati.

17 of 44

Flavio Poletti

Puoi ripetere?
http://www.perl.it/documenti/articoli/2006/07/puoi-ripetere.html
al suo interno "vede" le variabili $a e $b come alias di due particolari elementi della lista che
sono oggetto di confronto;
deve restituire un valore coerente con l'ordinamento atteso fra i due alias:
1 se $a maggiore (ossia, successivo) a $b;
0 se sono uguali (ossia possono potenzialmente essere scambiati nell'ordinamento
senza alterarne la bont);
-1 se $a minore (ossia, precedente) a $b.

Tutto questo traffico per un semplice ordinamento?!? In realt, visto che qualcosa che risulta
essere spesso utile, non deve sorprendere che nel caso numerico esista una scorciatoia chiamata
operatore navicella spaziale:
#!/usr/bin/perl
use strict;
use warnings;
my @disordinato = qw( 5 41 31 2 11 3 19 13 7 17 29 37 23);
my @ordinato = sort { $a <=> $b } @disordinato;
print "[ ", (join(", ", @ordinato)), " ]\n";
__END__
[ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41 ]

Spaziale veramente, no? Come? Ah, navicella spaziale, certo. Beh, se avete visto Guerre Stellari,
ricorderete certamente le navicelle da battaglia dell'Impero - esatto, quelle che avevano forma
<=>.
L'ultima forma - quella con la FUNZIONE - non aggiunge molta suspance rispetto a quella con
BLOCCO, ma sicuramente vi consente di aggiungere leggibilit vi d anche un pizzico di
flessibilit in pi. Tale approccio risulta infatti particolarmente adatto a gestire le seguenti
situazioni:
se il BLOCCO risulta particolarmente lungo pu essere difficile da leggere, poich sort e
LISTA sono distanti fra loro;
se volete decidere dinamicamente quale metodo di ordinamento (ad esempio, crescente o
decrescente, oppure numerico o alfabetico) utilizzare.
Vediamo un esempio:
#!/usr/bin/perl
use strict;
use warnings;
Perl Mongers Italia. Tutti i diritti riservati.

18 of 44

Flavio Poletti

Puoi ripetere?
http://www.perl.it/documenti/articoli/2006/07/puoi-ripetere.html
# L'array @funzioni contiene i riferimenti a quattro possibili funzioni
# di ordinamento; queste sono definite in fondo
my @funzioni = qw( alpha_c alpha_d num_c num_d );
#@funzioni = ( \&alpha_c, \&alpha_d, \&num_c, \&num_d );
# Per rendere leggibile la stampa diretta degli array nelle virgolette
$" = ", ";
my @disordinato = qw( 5 41 31 2 11 3 19 13 7 17 29 37 23 );
print("L'array disordinato e` [@disordinato]\n");
menu();
while (<>) {
# Controlli iniziali
next unless /^([1-5])/; # Salta i comandi non validi
last if $1 == 5;
# Esci se richiesto
# Ordinamento
my $funzione = $funzioni[$1 - 1];
# Trova funzione
my @ordinato = sort $funzione @disordinato; # ed usala
# Stampe
print("===> [@ordinato]\n");
menu();
}
print("E` stato un piacere\n");
sub menu {
print "
1. Alfabetico, crescente
2. Alfabetico, decrescente
3. Numerico, crescente
4. Numerico, decrescente
5. Esci
";
}
sub
sub
sub
sub

alpha_c
alpha_d
num_c
num_d

{
{
{
{

$a
$b
$a
$b

cmp
cmp
<=>
<=>

$b
$a
$b
$a

}
}
}
}

#
#
#
#

Alfabetico, crescente
Alfabetico, decrescente
Numerico, crescente
Numerico, decrescente

__END__
L'array disordinato e` [5, 41, 31, 2, 11, 3, 19, 13, 7, 17, 29, 37, 23]
1. Alfabetico, crescente
2. Alfabetico, decrescente
3. Numerico, crescente
4. Numerico, decrescente
5. Esci
2
===> [7, 5, 41, 37, 31, 3, 29, 23, 2, 19, 17, 13, 11]
1. Alfabetico, crescente
Perl Mongers Italia. Tutti i diritti riservati.

19 of 44

Flavio Poletti

Puoi ripetere?
http://www.perl.it/documenti/articoli/2006/07/puoi-ripetere.html
2. Alfabetico, decrescente
3. Numerico, crescente
4. Numerico, decrescente
5. Esci
4
===> [41, 37, 31, 29, 23, 19, 17, 13, 11, 7, 5, 3, 2]
1.
2.
3.
4.
5.
5
E`

Alfabetico, crescente
Alfabetico, decrescente
Numerico, crescente
Numerico, decrescente
Esci
stato un piacere

Si faccia attenzione ad un fatto: il nome della funzione pu essere contenuto in una variabile
scalare, ma solo nativa e non, ad esempio, parte di un array. Per capirci, abbiamo dovuto scrivere:
# Ordinamento
my $funzione = $funzioni[$1 - 1];
# Trova funzione
my @ordinato = sort $funzione @disordinato; # ed usala

non per particolare amore di leggibilit (anche se stato camuffato come tale), ma semplicemente
perch:
# Ordinamento
my @ordinato = sort $funzioni[$1 - 1] @disordinato; # ed usala

non avrebbe funzionato! La vita dura e l'interprete Perl, che ha a cuore la nostra educazione, a
volte l a ricordarcelo.
Si possono utilizzare sia i nomi delle funzioni che riferimenti ad esse, come avete modo di verificare
facilmente eliminando il commento dalla riga:
#@funzioni = ( \&alpha_c, \&alpha_d, \&num_c, \&num_d );

che imposta l'array, per l'appunto, con i riferimenti alle funzioni al posto dei nomi.
Emanuele Zeppieri mi fa inoltre notare che l'operatore di estrazione di reference - ok, il carattere
backslash - distributivo rispetto ad una lista, e che possiamo pertanto scrivere:
@funzioni = \( &alpha_c, &alpha_d, &num_c, &num_d );

Ma guarda tu che si inventano!


Una piccola avvertenza finale: per semplificare la stampa dell'array abbiamo modificato il valore
della variabile $" impostandola a ", ", ma in generale meglio evitare di farlo. O, se proprio
dovete (o volete), meglio localizzare la modifica il pi possibile utilizzando local all'interno di un
Perl Mongers Italia. Tutti i diritti riservati.

20 of 44

Flavio Poletti

Puoi ripetere?
http://www.perl.it/documenti/articoli/2006/07/puoi-ripetere.html
blocco, in modo da limitare la portata della modifica ed evitare di generare errori in altre parti del
codice. In questo caso, viste le dimensioni dello script, la cosa non crea comunque alcun problema.

Arma', questo me lo cacci via!


La seconda funzione di ciclo specializzato/generalizzato grep, che consente di filtrare una lista
trattenendo i soli elementi (qui sta la specializzazione) che verificano una determinata propriet
(qui la generalizzazione). Il principio ispiratore , ovviamente, il programma Unix grep.
Ci sono due modi di utilizzarla:
grep BLOCCO LISTA
grep ESPRESSIONE, LISTA

La differenza fra i due approcci sintattica e non ce ne occupiamo; vediamo invece alcuni esempi:
my
my
my
my
my

@lista
@inizia_con_i
@contiene_cifre
@solo_interi_minori_di_10
@solo_parole

=
=
=
=
=

qw( 12
grep {
grep {
grep {
grep {

ciao 6 7 frodo72 idromele isterica );


/^i/ } @lista;
/\d/ } @lista;
/^\d+$/ && $_ < 10 } @lista;
/^[[:alpha:]]+$/ } @lista;

Ciascun elemento della lista viene analizzato mediante il BLOCCO. In particolare, $_ nel blocco
diventa un alias per l'elemento della lista e pu essere utilizzato per effettuare il test desiderato; se
il test d esito positivo l'elemento passa in uscita, altrimenti viene bloccato.

Tempi moderni...
Avete mai visto il film di Charlie Chaplin "Tempi Moderni"? Se qualcuno ha detto no, in buona
compagnia: nemmeno io l'ho visto.
In ogni caso, una scena del film che spesso viene "citata" in televisione mostra Charlot in fabbrica,
davanti alla catena di montaggio. I pezzi passano, lui applica una trasformazione (un giro con la
chiave inglese) ed i pezzi proseguono sul nastro trasportatore.
map un po' la catena di montaggio delle trasformazioni sulle liste. Ci mettete davanti un blocco di
codice Charlot, infilate un po' di pezzi nella LISTA di ingresso, e map mette Charlot al lavoro su
ciascun pezzo per rimandarveli sulla LISTA di uscita.. Solo che, per fortuna, il blocco non soffre di
alienazione.
I possibili modi di utilizzarlo sono analoghi a quelli visti per grep:
map BLOCCO LISTA
Perl Mongers Italia. Tutti i diritti riservati.

21 of 44

Flavio Poletti

Puoi ripetere?
http://www.perl.it/documenti/articoli/2006/07/puoi-ripetere.html
map ESPRESSIONE, LISTA

Anche qui, non ci addentriamo in una dissezione delle differenze fra i due approcci, il manuale
sufficientemente chiaro! Alcuni esempi sono per d'obbligo per capire cosa fa veramente.
Nel primo, supponiamo di avere un array di valori numerici e di volerne calcolare i quadrati in un
nuovo array. Detto fatto:
my @input = 1 .. 10; # Per semplicita`, i numeri interi da 1 a 10
my @output = map { $_ * $_ } @input;

Esatto, proprio cos semplice. Venghino siori, non c' trucco e non c' inganno. immediato che
questa tecnica pu essere utilizzata per generalizzare una qualunque funzione scalare per lavorare
su un insieme di dati. La funzione matematica sin(), ad esempio, fatta per lavorare su un solo
valore per volta, ma se volete graficarla avete bisogno di calcolarla su un intervallo. Aspetta un
attimo: serve pure a calcolare l'intervallo!
# Calcoliamo la funzione sin(x) fra 0 e 2*PI, dividendo questo
# intervallo in 100 sotto-intervalli
my $intervalli = 100;
# Numero di sotto-intervalli
my $pi = 3.14159265358979;
# Pi greco
my $amp = $pi * 2 / $intervalli;
# Ampiezza sotto-intervallo
my @x = map { $amp * $_ } 0 .. $intervalli;
# I punti sono uno in piu`
my @y = map { sin($_) } @x;
# I valori...

map non serve solo ai malati di matematica! Potreste avere un array di parole, e volerle
trasformare in lettere maiuscole:
#!/usr/bin/perl
use strict;
use warnings;
my @parole = qw( ciao a tutti come al solito );
my @maiuscole = map { uc($_) } @parole;
$" = ", ";
print("parole: [@parole]\n");
print("maiuscole: [@maiuscole]\n");
__END__
parole: [ciao, a, tutti, come, al, solito]
maiuscole: [CIAO, A, TUTTI, COME, AL, SOLITO]

Come vedete, la catena di montaggio: le parole entrano sotto forma di $_, viene applicata la
funzione ed il valore restituito (ossia quello dell'ultima espressione immediatamente prima
dell'uscita dal blocco) inviato nella lista di uscita, che in questo caso va a popolare @maiuscole.
map, per, pu fare di pi. In particolare, nessuno impone al blocco di restituire un valore scalare;
Perl Mongers Italia. Tutti i diritti riservati.

22 of 44

Flavio Poletti

Puoi ripetere?
http://www.perl.it/documenti/articoli/2006/07/puoi-ripetere.html
restituendo una lista possibile ampliare o accorciare la lista in ingresso. Ad esempio, un rimpiazzo
un po' ingenuo per grep potrebbe essere il seguente:
my @valori = 1 .. 20;
my @maggiori_di_10 = map {
if ($_ > 10) { $_ } # Se verifica la proprieta`, fa' passare
else
{ () } # Altrimenti, blocca restituendo "niente"
} @valori;

Capite per bene quanto grep sia pi pulito e leggibile! Particolarmente interessante per la
possibilit di aumentare il numero di elementi, che pu essere utilizzato molto proficuamente per
generare una hash a partire da una lista di valori:
#!/usr/bin/perl
use strict;
use warnings;
# Generiamo una hash in cui le chiavi sono le parole di una lista, ed
# i valori sono le lunghezze delle parole stesse
my %lunghezza_di = map { $_ => length($_) } qw( ciao a tutti basta! );
print("La lunghezza di 'ciao' risulta essere ", $lunghezza_di{'ciao'}, "\n");
__END__
La lunghezza di 'ciao' risulta essere 4

Come darsi la zappa sui piedi


Avrete sicuramente notato che in tutte e tre le funzioni vengono creati degli alias ai dati nella lista
originale. Che vuol dire? Ah, ma allora non leggete: abbiamo gi parlato degli alias nella parte
relativa a for/foreach!
Ok, se non volete andare a rivedervela, ricordiamo semplicemente che le variabili alias (ossia $a e
$b in sort, e $_ in grep e map) non sono delle copie, ma agiscono direttamente sugli elementi
della lista in ingresso. Con l'ovvia conseguenza che vi consentono di modificare l'elemento della
lista in ingresso.
Tale possibilit pu lasciare stupiti nelle nostre tre funzioni: in fondo, queste sono pensate per
agire come filtri, per cui dovrebbero considerare gli ingressi in sola lettura. Vanno per fatte un
paio di considerazioni:
1. nessuno vi obbliga a modificare i valori della lista in ingresso anche se potete farlo. Perl non
un linguaggio da scimmie, al contrario mette a disposizione del programmatore moltissima
"potenza di fuoco", lasciandogli il compito di mettere la sicura quando vuole avventurarsi in
sentieri perigliosi;
Perl Mongers Italia. Tutti i diritti riservati.

23 of 44

Flavio Poletti

Puoi ripetere?
http://www.perl.it/documenti/articoli/2006/07/puoi-ripetere.html
2. utilizzare delle copie porterebbe ad un appesantimento dell'eseguibile a scapito
dell'efficienza.
Andando a stringere, utilizzate questa caratteristica di aliasing solo se siete molto consapevoli di
ci che state per fare!

Vita reale: ordinare le chiavi di una hash secondo i valori


Tutti, dico tutti sapete benissimo che la funzione keys restituisce le chiavi di una hash. E tutti
sapete anche che la lista restituita ha un ordinamento che si perde nelle radici di Perl - come a dire
che non ne ha uno.
Come fare ad ordinare le chiavi di una hash secondo i valori puntati da ciascuna chiave? Presto
detto, per sort non potrebbe essere pi semplice:
my %hash;
# ... %hash viene popolata con dati vitali! ...
# Usiamo <=> supponendo che siano valori numerici. Se i valori sono
# parole, e` meglio usare cmp :)
my @chiavi_ordinate = sort { $hash{$a} <=> $hash{$b} } keys %hash;

Molto semplicemente il confronto viene effettuato fra i valori associati invece che direttamente fra
$a e $b. Ci avevate gi pensato, vero?

Vita reale: la trasformazione di Schwartz


Randal L. Schwartz un punto di riferimento nella comunit Perl, per i contributi dati (Learning
Perl suo, ad esempio). Una speciale tecnica di ordinamento ha avuto talmente tanto successo da
prendere il suo nome, vediamo come funziona. Attenzione: alla prima lettura vi sembrer solo
rumore!
L'ipotesi di base che vogliamo effettuare l'ordinamento di una lista di elementi basandoci su una
funzione di costo calcolabile per ciascuno di essi. Il problema, per, che la funzione molto
lenta, ed una cosa tipo:
my @ordinati = sort { costo($a) <=> costo($b) } @disordinati;

costringe sort a calcolarla due volte per ciascun confronto deve fare. Se il primo elemento della
lista di ingresso viene confrontato con tutti gli altri, dunque, la funzione costo viene calcolata ogni
volta! Inaccettabile.

Perl Mongers Italia. Tutti i diritti riservati.

24 of 44

Flavio Poletti

Puoi ripetere?
http://www.perl.it/documenti/articoli/2006/07/puoi-ripetere.html
Schwartz, riutilizzando un'idea gi diffusa in programmazione, ha pensato bene di pre-calcolare il
valore della funzione di costo una volta per tutte, e di utilizzare questo valore calcolato a priori per
l'ordinamento. Concettualmente si pu fare cos:
# Calcolo i costi in una hash
my %costo_di = map { $_ => costo($_) } @disordinati;
# Ora ordino @disordinati utilizzando la hash
my @ordinati = sort { $costo_di{$a} <=> $costo_di{$b} } @disordinati;

Schwartz non era per soddisfatto da questa soluzione, che lo costringeva a fare l'ordinamento in
due istruzioni anzich in una. D'altra parte, con una hash non avrebbe potuto fare di meglio.
allora giunto alla seguente formulazione:
my @ordinati = map { $_->[0] }
sort { $a->[1] <=> $b->[1] }
map { [ $_, costo($_) ] }
@disordinati;

Eh? Piano, piano. Ricordiamoci della catena di montaggio: gli ingressi sono a destra, le uscite a
sinistra. Bene, i nostri dati @disordinati entrano dentro la map pi in basso, che in uscita d
una curiosa trasformazione sul dato: per ciascun elemento, costruisce un array anonimo in cui il
primo valore l'elemento stesso, altrimenti ce lo perderemmo, mentre il secondo il valore della
funzione costo().
A questo punto interviene sort che, ricordiamolo, riceve una lista di riferimenti ad array - quelli
generati dalla map descritta. Poich gli array in questione contengono la funzione di costo all'indice
1, il blocco imposta un confronto fra i corrispondenti campi dei due riferimenti $a e $b. In uscita,
sort restituisce gli stessi elementi ricevuti (o, meglio, delle copie): riferimenti ad array di due
elementi ciascuno, ordinati in base alla funzione costo.
Il problema che a noi non servono questi array, a noi servono gli elementi che stavano in
@disordinati. precisamente per questo motivo che c' la map pi in alto: questa restituisce
semplicemente il primo elemento di ciascuno di questi array - ossia il valore dell'elemento di
interesse, che avevamo prudentemente inserito.
Questa una delle trasformazioni di ordinamento pi popolari in Perl - capito perch tutti dicono
che un programma Perl assomiglia al rumore di una linea telefonica?

List::Util
L'approccio specialistico delle funzioni sort, grep e map ha fatto venire l'acquolina in bocca a
Perl Mongers Italia. Tutti i diritti riservati.

25 of 44

Flavio Poletti

Puoi ripetere?
http://www.perl.it/documenti/articoli/2006/07/puoi-ripetere.html
Graham Barr, autore di List::Util, forse uno dei moduli pi trascurati del CORE di Perl. Si, avete
capito bene: List::Util ve lo ritrovate in una qualsiasi installazione tipica di Perl.
Come dicevo, a Barr era venuta fame: "Come faccio a trovare il valore massimo di una lista? Ed il
minimo? Ed il primo che verifica una certa propriet? Se voglio sommare tutti gli elementi? O
mischiare un po' le carte?". Beh, ha risolto questi problemi comuni e li ha messi in List::Util.
Quasi tutte le funzioni hanno il seguente schema di chiamata:
min
max
sum

LISTA
LISTA
LISTA

# Trova il minimo (numerico)


# Trova il massimo (numerico)
# Somma tutti i valori (numerici)

minstr
maxstr

LISTA
LISTA

# Trova il minimo (alfabetico)


# Trova il massimo (alfabetico)

shuffle LISTA

# Da` una versione mischiata di LISTA

L'ultima funzione restituisce una lista, mentre tutte le altre restituiscono uno scalare. Ad esempio:
use List::Util qw( minstr maxstr shuffle ); # Non dimentichiamolo!
my @lista;
# ... piu` tardi, quando la lista e` bella piena...
# Troviamo la stringa 'minima', nell'ordinamento alfabetico
my $minima = minstr @lista;
# Ora, la massima
my $massima = maxstr @lista;
# Diamo una mischiatina
my @mischiata = shuffle @lista;

Insomma, avete capito.


Ci sono poi altre due funzioni, first e reduce, che funzionano molto pi similmente a map:
first BLOCCO LISTA
reduce BLOCCO LISTA

La prima piuttosto semplice: utilizza BLOCCO molto similmente a grep, ossia per effettuare un
test sull'elemento $_; la prima volta che tale test va a buon fine l'iterazione si interrompe,
restituendo l'elemento corrispondente.
use List::Util qw( first );
my @lista;
# ... piu` tardi, quando la lista e` bella piena...
Perl Mongers Italia. Tutti i diritti riservati.

26 of 44

Flavio Poletti

Puoi ripetere?
http://www.perl.it/documenti/articoli/2006/07/puoi-ripetere.html
# Troviamo il primo valore numerico intero
my $primo_numero_intero = first { /^\d+$/ } @lista;

La funzione reduce, invece, va un po' pi in l. talmente generica che farete molto in fretta a
scordarvi che esiste, ma se riuscite a dominarla avete la mia ammirazione! Ad ogni iterazione,
BLOCCO ha a disposizione due variabili $a e $b, proprio come sort. Come vengono impostate?
Qui sta il bello:
1. alla prima iterazione vengono presi i primi due elementi della lista, il primo $a ed il
secondo potete immaginarvelo;
2. nelle iterazioni successive $a il valore restituito da BLOCCO al giro precedente, mentre $b
il successivo valore in LISTA.
Come casi particolari, se la lista vuota viene restituito undef, mentre se ha un solo elemento vie

List::MoreUtils
Dopo aver fatto venire l'acquolina in bocca a tutti con List::Util, Graham Barr ha deciso bene di
metterci a dieta limitandosi a fornire le otto funzioni viste in precedenza. Del resto piuttosto
chiaro nella documentazione, riferendosi alle varie proposte come:

[aggiunte] che sono state richieste, ma che sono stato riluttante ad aggiungere per il
fatto che sono molto semplici da implementare in Perl
Ci sono per alcune considerazioni da fare a riguardo:
l'intento di List::Util fornire una raccolta di funzioni che migliorano la leggibilit del codice
pur essendo tutte implementabili molto semplicemente in Perl, per cui il suo argomento
potrebbe essere parimenti applicato alle funzioni che ha deciso di includere;
un altro grande vantaggio fornito da List::Util risiede nel fatto che le funzioni sono
implementate in due forme, ossia in Perl puro ed in C. Questo fa s che, avendo a
disposizione un compilatore C, l'utente possa accedere ad una implementazione pi
efficiente del dato algoritmo, garantendo comunque l'utente pi svantaggiato mediante
l'implementazione puramente in Perl.
Tutto sommato, mia opinione che Barr non avesse semplicemente il tempo o la voglia di stare
dietro a tutte le proposte! Il testimone stato per felicemente raccolto da Tassilo von Parseval in
List::MoreUtils, che d appunto accoglienza ad una lunga serie di algoritmi sulle liste che non
hanno trovato posto nei locali esclusivi di List::Util. Anche qui, inutile a dirsi, sono presenti le due
Perl Mongers Italia. Tutti i diritti riservati.

27 of 44

Flavio Poletti

Puoi ripetere?
http://www.perl.it/documenti/articoli/2006/07/puoi-ripetere.html
implementazioni in Perl puro ed in C, in modo da accontentare tutti.
Il modulo contiene veramente tante funzioni, per cui il consiglio per chi volesse vederle tutte di
andare a guardare la documentazione relativa. Alcune funzioni, per, sembra proprio che siano
state escluse ingiustamente da List::Util, per cui cercheremo di render loro giustizia.

any ed all: la generalizzazione di or ed and


Pu capitare di dover verificare se una determinata lista contiene almeno un elemento con una
determinata propriet. Non sto parlando solo di programmazione, ma anche di vita reale; ad
esempio, inserendo canzoni nel nostro lettore MP3, potremmo ad un certo punto chiederci se
abbiamo messo qualcosa dei Led Zeppelin, senza di che non saremmo contenti (ognuno si
accontenta come pu!). In questo caso, quindi, dobbiamo sostanzialmente rispondere alla
domanda: "di tutte le canzoni caricate, ne esiste qualcuna il cui artista 'Led Zeppelin'?". Qualcosa
che in Perl suonerebbe pi o meno cos:
my @canzoni = seleziona_brani('a caso');
# C'e` qualcosa dei Led Zeppelin?
if (any { autore($_) eq 'Led Zeppelin' } @canzoni) {
print "tranquillo, ci sono i Led Zeppelin\n";
}
else {
print "una grave mancanza, a cui rimediamo subito!\n";
push @canzoni, seleziona_brani('Led Zeppelin');
}

Beh, se utilizzate List::MoreUtils proprio quello che dovete scrivere! La funzione any
(letteralmente: qualcuno), infatti, incapsula proprio un controllo di esistenza di almeno un
elemento della lista che soddisfa il predicato impostato, che in questo caso si risolve in un controllo
sull'autore del brano. La ricerca ovviamente efficiente, nel senso che viene interrotta non appena
uno degli elementi verifica la propriet data; in questo senso, dunque, any pu essere visto come
una generalizzazione dell'operatore or, che "si ferma" se l'espressione a sinistra vera:
if (

autore($canzoni[0]) eq 'Led
or autore($canzoni[1]) eq 'Led
or autore($canzoni[2]) eq 'Led
or autore($canzoni[3]) eq 'Led

Zeppelin'
Zeppelin'
Zeppelin'
Zeppelin') { ... }

In questo caso, se la prima canzone (quella di indice 0) soddisfa il requisito, le altre tre condizioni
non vengono mai controllate, in quanto sarebbe un'inutile perdita di tempo (la or globale sarebbe
vera comunque). I vantaggi nell'uso di any sono per evidenti:
applicabile qualunque sia la lunghezza della lista;
Perl Mongers Italia. Tutti i diritti riservati.

28 of 44

Flavio Poletti

Puoi ripetere?
http://www.perl.it/documenti/articoli/2006/07/puoi-ripetere.html
evita ripetizione di codice e possibilit di errore;
esprime pi chiaramente cosa si sta tentando di fare.
Avrete sicuramente riconosciuto che lo stile di chiamata analogo a quello di map e compagni, in
modo da ridurre lo stress per il programmatore e consentirgli di concentrarsi su quello che conta
realmente.
Se any risponde alla domanda "ne esiste almeno uno?", dall'altro lato all (letteralmente: tutti)
risponde ad una richiesta pi stringente "lo fanno tutti?" - costituendo quindi una generalizzazione
di and:
# Diamo una controllata al genere delle canzoni
if (all { genere($_) eq 'rock' } @canzoni) {
print "ma ascolti solamente rock?!?\n";
}

Delle due funzioni esistono anche le rispettive forme negate:


none (letteralmente: nessuno) restituisce un valore vero se nessun elemento della lista
verifica una data propriet, costituendo quindi la negazione di any;
notall (letteralmente: non tutti) restituisce un valore vero se non tutti gli elementi della
lista verificano il predicato nel blocco, ed dunque il contrario di all come suggerisce il
nome.

Quanti sono?
Quante monete da 1, 2 o 5 centesimi ho nel borsellino?
Quanti libri di informatica ci sono nella libreria?
Quanta roba scaduta ho nel frigo?
In tutti questi casi non mi interessa sapere quali sono, solo quanti. Certo, sarebbe forse il caso di
sapere anche quali nell'ultimo caso, ma non il caso di stare a sottilizzare.
Per domande di questo tipo vengono in nostro aiuto true e la sua controparte false, che
effettuano proprio questo tipo di conteggi basandosi, come al solito, su un blocco di codice che
funge da predicato:
my $n_spiccetti = true { $_ <= 0.05 } @borsellino;
my $n_libri_informatica = true { argomento($_) eq 'informatica' } @libreria;
my $n_scaduti = false { ancora_buono($_) } @frigo;
Perl Mongers Italia. Tutti i diritti riservati.

29 of 44

Flavio Poletti

Puoi ripetere?
http://www.perl.it/documenti/articoli/2006/07/puoi-ripetere.html
Nell'ultimo caso utilizziamo false, trasferendo quindi la negazione fuori dal blocco; si sarebbe
potuto scrivere anche:
my $n_scaduti = true { not ancora_buono($_) } @frigo;

ma forse (e dico forse) la leggibilit sarebbe stata inferiore. Dipende dai gusti.

minmax, o come fare due cose contemporaneamente


Dovete graficare una funzione ed avete due liste, una per asse:
my @x = 1 .. 20;
my @y = f(@x);

Vogliamo che il nostro grafico sia posizionato automaticamente dove si trovano i dati, ossia
vogliamo scegliere gli intervalli per i due assi in modo da coincidere con i valori minimo e massimo
contenuti nei due array. L'intervallo per l'asse delle x semplice da determinare - basta prendere i
due estremi - ma come facciamo per determinare l'intervallo sull'asse delle y? Abbiamo visto che
List::Util contiene le due funzioni min e max, per cui potremmo dire:
my ($min_x, $max_x) = @x[1, -1]; # Prendi il primo e l'ultimo valore da @x
my $min_y = min @y;
my $max_y = max @y;

Questo modo di calcolare minimo e massimo non proprio... il massimo. La lista @y viene infatti
scandita due volte, e ad ogni scansione abbiamo un numero di confronti pari al numero di elementi
meno uno.
Esiste invece un algoritmo che consente di evitare un po' di confronti nel caso si voglia calcolare
contemporaneamente sia il minimo che il massimo (v. ad esempio http://www.perlmonks.org
/?node_id=507340); tale algoritmo stato implementato in C nella funzione minmax, il che mette
a disposizione un sistema efficiente ed espressivo per svolgere questa doppia ricerca:
my ($min_x, $max_x) = @x[1, -1]; # Prendi il primo e l'ultimo valore da @x
my ($min_y, $max_y) = minmax @y; # Prendi minimo e massimo da @y

Un po' di confronti a questo punto non guastano (esempio preso da http://www.perlmonks.org


/?node_id=507414):
#!/usr/bin/perl
use strict;
use warnings;
use List::Util qw( min max reduce );
use List::MoreUtils qw( minmax );
use Benchmark qw( cmpthese );
Perl Mongers Italia. Tutti i diritti riservati.

30 of 44

Flavio Poletti

Puoi ripetere?
http://www.perl.it/documenti/articoli/2006/07/puoi-ripetere.html
my @array = map { rand 100 } 1 .. 1_000_000;
my $count = 0;
cmpthese -10, {
util_min_max => sub {
my $min = min(@array);
my $max = max(@array);
},
util_reduce => sub {
my ( $min, $max ) = @{ (reduce {
my $r= ref $a ? $a : [($a) x 2];
if ($b < $r->[0]) {
$r->[0]= $b;
} elsif ($b > $r->[1]) {
$r->[1]= $b;
}
$r
} @array) };
},
mu_minmax => sub {
my ( $min, $max ) = minmax(@array);
},
};
__END__
util_reduce
util_min_max
mu_minmax

Rate
0.846/s
10.5/s
19.2/s

util_reduce
-1146%
2169%

util_min_max
-92%
-82%

mu_minmax
-96%
-45%
--

Ok, a parte il fatto che magari dovrei scrivere qualcosa su Benchmark, concentriamoci sulla
colonna Rate della tabella finale: come potete vedere, l'utilizzo di minmax consente di eseguire
19.2 ricerche al secondo su un milione di elementi, mentre l'utilizzo di min e max separate ce ne fa
fare solo 10.5 al secondo. Come ulteriore candidato stato utilizzato un approccio utilizzante
reduce, che per sconta il fatto che la parte di confronti viene sviluppata in Perl, perdendo
dunque i vantaggi derivanti dall'implementazione C delle altre funzioni.

Dimmi dove sono


A volte necessario lavorare con gli indici piuttosto che direttamente con i valori contenuti nella
lista da analizzare. Un caso tipico potrebbe essere quello in cui abbiamo due liste fra loro
accoppiate, ossia in cui elementi che si trovano nella stessa posizione sono logicamente associati
fra loro, e vogliamo estrarre tutti gli elementi dalla seconda lista per cui il corrispondente elemento
nella prima soddisfi una determinata propriet.
Ok, sento urlare "Esempio! Esempio!" e non indugio oltre. Supponiamo di avere la funzione sin()
Perl Mongers Italia. Tutti i diritti riservati.

31 of 44

Flavio Poletti

Puoi ripetere?
http://www.perl.it/documenti/articoli/2006/07/puoi-ripetere.html
calcolata su un intervallo dell'asse delle x corrispondente all'angolo giro centrato intorno allo 0:
my $rapporto = 3.14159265358979 / 180;
my @x = -179 .. 180; # 360 gradi, passo 1 grado
my @y = map { sin( $_ * $rapporto ) } @x;

La funzione seno lavora su gradi radianti, per cui si rende necessaria la conversione. Supponiamo
ora che ci interessi avere tutti quei valori di angoli per cui la funzione , ad esempio, maggiore di
0.5. Mentre facile estrarre i valori stessi, utilizzando ad esempio grep come visto in precedenza:
my @y_maggiore_05 = grep { $_ > 0.5 } @y;

non altrettanto semplice per i corrispondenti valori sull'asse delle x, richiedendo in generale di
iterare sugli indici:
my @x_di_y_maggiore_05;
for my $indice (0 .. $#x) {
if ( $y[$indice] > 0.5 ) {
push @x_di_y_maggiore_05, $x[$indice];
}
}

La funzione indexes fa proprio al caso nostro, consentoci di aggiungere una leggibilit senza pari:
my @indici
= indexes { $_ > 0.5 } @y;
my @x_di_y_maggiore_05 = @x[@indici];
my @y_maggiore_05
= @y[@indici];

Algorithm::Loop
Un altro che ha deciso che non aveva abbastanza modi differenti per iterare Tye McQueen. Il
quale ha deciso di mettere tutto - ed un po' di pi - in Algorithm::Loop. Questo modulo molto
grande, per cui meglio evitare una rassegna puntuale in favore di un mordi-e-fuggi.

map

non abbastanza, voglio di pi!

Una prima funzione utilissima Filter, che - lo credereste? - utile per filtrare liste. Lo so, lo so,
abbiamo gi map per quello. Per l'autore del modulo sa il fatto suo, per cui meglio andare
avanti.
Supponiamo che abbiate una lista di stringhe di testo, e che abbiate bisogno di sostituire la parola
Tizio alla parola Caio in ciascuna di esse. Semplice: utilizziamo l'operatore s/// e siamo a cavallo.
Meno semplice: l'operatore s/// modifica ci su cui agisce (dandoci la zappa sui piedi), e per di
pi restituisce il numero di match trovati! In poche parole:
Perl Mongers Italia. Tutti i diritti riservati.

32 of 44

Flavio Poletti

Puoi ripetere?
http://www.perl.it/documenti/articoli/2006/07/puoi-ripetere.html
my @filtrate = map { s/Tizio/Caio/g } @stringhe;

semplicemente, incontrovertibilmente sbagliato. A meno che non vogliate modificare stringhe


ed avere un array con il numero di modifiche fatte, ovviamente. Una soluzione copiare il dato,
modificare la copia e restituirla:
my @filtrate = map {
my $copia = $_;
$copia =~ s/Tizio/Caio/g;
$copia;
} @stringhe;

# Fai un backup
# Modifica la copia, l'originale e` al sicuro
# Non dimentichiamoci di restituire $copia!

Wow. Che bello sarebbe se... Ecco, Filter serve appunto a questo:
use Algorithm::Loops qw( Filter );
my @stringhe;
# ... piu` tardi, quando tutti dormono...
my @filtrate = Filter { s/Tizio/Caio/g } @stringhe;

Proprio quello che volevamo intendere con la prima map. Filter pu essere utilizzata con tutte
quelle funzioni od operatori che effettuano modifiche direttamente sui loro argomenti, come ad
esempio chomp o tr///.

Hey, devo iterare su tre array contemporaneamente!


Un altro gruppo di funzioni interessanti in Algorithm::Loops costituito dalla famiglia MapCar*.
Fanno tutte la stessa cosa - iterare su pi array contemporaneamente - solo che si comportano in
maniera differente quando gli array hanno dimensioni differenti. Vediamo un esempio.
Supponiamo di essere in un museo e di ricevere dati da differenti strumenti di misura, vagamente
sincronizzati fra loro. Vi ritrovate dunque con un po' di array, e volete costruire una hash indicizzata
con il tempo di ciascuna misura:
#!/usr/bin/perl
use strict;
use warnings;
use Algorithm::Loops qw( MapCarMin );
use Data::Dumper;
my
my
my
my

@timestamp;
@temperatura;
@umidita;
@visitatori;

# A che ora e` la misura

# Simuliamo l'arrivo dei dati


while (<DATA>) {
Perl Mongers Italia. Tutti i diritti riservati.

33 of 44

Flavio Poletti

Puoi ripetere?
http://www.perl.it/documenti/articoli/2006/07/puoi-ripetere.html
chomp();
my @dati = split /:/;
push @timestamp, $dati[0];
push @temperatura, $dati[1];
push @umidita, $dati[2];
push @visitatori, $dati[3];
}
# Ora torniamo al mondo reale, calcoliamo le statistiche
my %statistiche = MapCarMin {
my ($timestamp, @dati) = @_; # Siamo in una funzione, di fatto
$timestamp => \@dati;
# Stesso trucco di map
} \@timestamp, \@temperatura, \@umidita, \@visitatori;
# Stampe, saluti e baci
print(Dumper(\%statistiche));
__DATA__
9.30:24.5:80%:12
10.00:25.5:83%:18
10.30:26.0:85%:23

Come indicato dal commento, ci troviamo in realt all'interno di una funzione, anche se non
presente la parola chiave sub immediatamente prima dell'apertura del blocco. Se volete sapere
come si fa, studiatevi i prototipi in perlsub. Notate che al posto del blocco potete anche mettere un
riferimento a sub.
Alla funzione viene passato un array @_, come di consueto. Gli elementi di questo array sono presi
dagli array su cui stiamo iterando, uno ad uno nell'ordine (anche se questa regola pu cambiare a
seconda della particolare variet di MapCar* che si sta utilizzando). In questo caso stiamo
utilizzando MapCarMin, che itera finch non esaurisce almeno uno degli array. La stampata la
seguente:
$VAR1 = {
'10.00' => [
'25.5',
'83%',
'18'
],
'9.30' => [
'24.5',
'80%',
'12'
],
'10.30' => [
'26.0',
'85%',
'23'
]
};
Perl Mongers Italia. Tutti i diritti riservati.

34 of 44

Flavio Poletti

Puoi ripetere?
http://www.perl.it/documenti/articoli/2006/07/puoi-ripetere.html

Permutazioni
No, non sono modificazioni genetiche. E nemmeno permaturazioni di togniazziana memoria. Una
permutazione di una lista semplicemente un modo per scrivere gli stessi elementi in un altro
ordine. L'insieme di tutti i possibili modi (ossia ordini) con cui possiamo scrivere la lista detto
insieme delle permutazioni.
A rigor di logica, le funzioni NextPermute* non consentono di implementare un ciclo; al
contrario, consentono di trovare l'elemento successivo nell'insieme delle permutazioni di una lista.
Potrebbero servirvi raramente - ma se vi servono? Beh, Algorithm::Loops sar l per servirvi!
Supponiamo dunque di voler trovare tutte le permutazioni della lista dei primi tre numeri primi:
#!/usr/bin/perl
use strict;
use warnings;
use Algorithm::Loops qw( NextPermuteNum );
# La lista di partenza
my @lista = qw( 5 3 2 );
# Creiamo una copia ordinata della lista
my @lista_iterata = sort { $a <=> $b } @lista;
# Partenza!
$" = ", ";
do {
print("[@lista_iterata]\n");
} while (NextPermuteNum(@lista_iterata));
__END__
[2,
[2,
[3,
[3,
[5,
[5,

3,
5,
2,
5,
2,
3,

5]
3]
5]
2]
3]
2]

Due osservazioni sulla lista iterata:


viene modificata sul posto, per cui utilizzate una copia (come nell'esempio) per non rovinare
la lista originale;
va passata ordinata in ordine crescente (da cui la nostra vecchia conoscenza, sort). Questo
requisito piuttosto blando, poich la funzione deve trovare tutte le permutazioni.

NestedLoops
Perl Mongers Italia. Tutti i diritti riservati.

35 of 44

Flavio Poletti

Puoi ripetere?
http://www.perl.it/documenti/articoli/2006/07/puoi-ripetere.html
Eccoci arrivati alla fine. La soluzione estrema. Quello che avete sempre temuto e rimandato.
NestedLoops.
Vi tocca gestire dei cicli annidati. Tocca a voi, avete cercato di evitarlo, avete cercato di farlo fare a
qualcun altro ma niente, dovete farlo voi. Tanti cicli annidati, fino ad un livello incredibile. Non
voglio sapere perch e percome, io vi dico come fare e basta, ok? Anzi, ve lo dice
Algorithm::Loops.
Supponiamo che abbiate quella bella hash degli spettacoli cinematografici di prima, e che vogliate
stamparla tutta. La soluzione immediata sarebbe la seguente:
#!/usr/bin/perl
use strict;
use warnings;
my %cinema;
while (<DATA>) {
chomp(); # Rimuovi a-capo alla fine
my ($citta, $sala, $titolo, $orario) = split /:/;
push @{$cinema{$citta}{$sala}{$titolo}}, $orario;
}
for my $citta (keys %cinema) {
for my $sala (keys %{$cinema{$citta}}) {
for my $titolo (keys %{$cinema{$citta}{$sala}}) {
my @orari = @{$cinema{$citta}{$sala}{$titolo}};
print("$citta\t$sala\t$titolo\t@orari\n");
}
}
}
__DATA__
Roma:Alcazar:Madagascar:18.20
Roma:Alcazar:Madagascar:20.30
Roma:Alcazar:Madagascar:22.40
Milano:Trianon:Madagascar:19.45
Milano:Trianon:Madagascar:21.45
Milano:Alcazar:Madagascar:16.00
Milano:Alcazar:Madagascar:18.00
Milano:Alcazar:Madagascar:20.00
Roma:Alcazar:Seven Swords:21.00
Roma:Alcazar:Seven Swords:23.00

Stampa:
Milano
Milano
Roma
Roma

Trianon
Alcazar
Alcazar
Alcazar

Madagascar
Madagascar
Seven Swords
Madagascar

19.45
16.00
21.00
18.20

21.45
18.00 20.00
23.00
20.30 22.40

Perl Mongers Italia. Tutti i diritti riservati.

36 of 44

Flavio Poletti

Puoi ripetere?
http://www.perl.it/documenti/articoli/2006/07/puoi-ripetere.html
Funziona! Peccato che sia bruttino, e soprattutto sia poco leggibile ed un po' complicato da
maneggiare quando si decida di inserire altre "colonne". Lo stesso problema pu essere risolto con
NestedLoops:
#!/usr/bin/perl
use strict;
use warnings;
use Algorithm::Loops qw( NestedLoops );
use Data::Dumper;
my %cinema;
while (<DATA>) {
chomp(); # Rimuovi a-capo alla fine
my ($citta, $sala, $titolo, $orario) = split /:/;
push @{$cinema{$citta}{$sala}{$titolo}}, $orario;
}
NestedLoops(
[
[ ['' => \%cinema] ], # Loop esterno "finto"
( # Ciascuna iterazione fa sempre la stessa cosa, ossia
# iterare sulle chiavi di un riferimento ad hash. Usiamo
# pero' una coppia passando anche la chiave, in modo
# da averla a disposizione per la stampa finale
sub {
my ($key, $hashref) = @$_;
[ map { [$_ => $hashref->{$_}]} keys %$hashref ];
}
) x 3, # 3 volte, una per %cinema, le altre per citta e sala
],
sub { # Stampe
# @_ contiene tutte le coppie
my $last_ref = $_[$#_]->[1]; # Riferimento agli orari
my @chiavi = map { $_->[0] } @_; # Estrai le chiavi
shift @chiavi; # La prima chiave e` fittizia
local $, = "\t";
local $\ = "\n";
print(@chiavi, "@$last_ref");
}
);
__DATA__
Roma:Alcazar:Madagascar:18.20
Roma:Alcazar:Madagascar:20.30
Roma:Alcazar:Madagascar:22.40
Milano:Trianon:Madagascar:19.45
Milano:Trianon:Madagascar:21.45
Milano:Alcazar:Madagascar:16.00
Milano:Alcazar:Madagascar:18.00
Milano:Alcazar:Madagascar:20.00
Roma:Alcazar:Seven Swords:21.00
Roma:Alcazar:Seven Swords:23.00

Perl Mongers Italia. Tutti i diritti riservati.

37 of 44

Flavio Poletti

Puoi ripetere?
http://www.perl.it/documenti/articoli/2006/07/puoi-ripetere.html
Uscita:
Milano
Milano
Roma
Roma

Trianon
Alcazar
Alcazar
Alcazar

Madagascar
Madagascar
Seven Swords
Madagascar

19.45
16.00
21.00
18.20

21.45
18.00 20.00
23.00
20.30 22.40

Non avremmo accettato niente di differente.


Piano con le proteste, lo so che sembra (un po') esoterico. E maledettamente complicato. Ma
occorre notare che questo gioiellino in grado di gestire praticamente qualunque livello di
indentazione, in virt della semplice riga:
) x 3, # 3 volte, una per %cinema, le altre per citta e sala

Potente, eh? Ok, ok, passiamo a come funziona.


NestedLoops accetta tre dati in ingresso:
la descrizione di ci su cui deve iterare, sotto forma di array anonimo (come in [ '' =>
\%cinema ]) o di sub che restituisce un array anonimo (come nella sub ripetuta tre
volte). La descrizione di ciascun passo del ciclo viene passata a sua volta come array
anonimo, e costituisce il primo dato passato a NestedLoops;
opzionalmente, un riferimento ad una hash contenente alcune opzioni di iterazione, che nel
nostro caso non abbiamo utilizzato;
infine, ma opzionale anche questo, un riferimento ad una sub da chiamare ad ogni
iterazione.
Il primo parametro, dunque, dice a NestedLoop quante e quali iterazioni deve effettuare,
mediante un array anonimo. Il primo elemento dell'array descrive il ciclo pi esterno, e man mano
si trovano i cicli innestati. Nel nostro caso, lo spunto di partenza pu sembrare strano:
[ '' => \%cinema ], #...

ma solo funzionale alla logica del resto della visita. Poich stiamo visitando una struttura con
hash innestate dentro altre hash, a ciascun passo possiamo estrarre le chiavi al particolare livello in
cui ci troviamo. Estendiamo allora questo concetto anche alla prima iterazione, per cui facciamo in
modo da passare un riferimento a %cinema.
Perch usare l'array anonimo a due posizioni, per? Ricordiamoci che non vogliamo solo arrivare
agli orari, ma vogliamo anche stampare citt, sala e titolo del film, per cui dobbiamo portarci dietro
queste informazioni. In ciascuna iterazione, dunque, restituiamo un riferimento ad un array (come
Perl Mongers Italia. Tutti i diritti riservati.

38 of 44

Flavio Poletti

Puoi ripetere?
http://www.perl.it/documenti/articoli/2006/07/puoi-ripetere.html
richiesto) contenente tutte queste coppie chiave/valore inserite all'interno di un array anonimo (in
modo che venga passata la coppia tutta insieme). Tale processo viene ripetuto per 3 volte, la
prima per entrare dentro %cinema, le altre due per gestire il livello della citt e quello della sala.
L'ultimo livello, quello del film, non va iterato perch quello nel quale effettuiamo le stampe.
La sub da eseguire nel ciclo pi interno riceve una lista di tutte i dati estratti, a ciascun livello, da
ognuna delle descrizioni. Nel nostro caso, dunque, riceve una lista di array anonimi contenenti
coppie chiave/valore, una per ciascun livello. Vengono allora estratti il riferimento agli orari,
contenuto nell'ultima coppia, e le varie chiavi trovate nei differenti livelli, con l'accortezza che il
primo livello viene eliminato perch fittizio.

Esoterismi
Pensavate che Algorithm::Loops fosse gi al di l di quello che vi verr mai in mente di utilizzare,
eh? Come? Vi eravate gi fermati a map? Datemi retta, quando avrete utilizzato map un paio di
volte non vorrete pi farne a meno... ma sto divagando.
Passiamo un attimo in rassegna le cose pi esoteriche che abbiamo visto:
map stato probabilmente il primo, e ci consente di implementare una piccola catena di
montaggio;
seguono le varie contorsioni possibili con redo in un blocco;
reduce richiede senza dubbio un po' pi di un paio di volte per essere compreso a fondo
(ed essere abbandon*cough* adottato);
le ultra-specializzazioni di List::MoreUtils richiedono una memoria da elefante solo per sapere
che esistono...
NestedLoops potentissimo - come negarlo? - ma per raggiungere una certa fluidit nel suo
utilizzo bisogner forse sbatterci la testa contro un po' troppe volte.
Direi che ce n' abbastanza, ma no! Siamo affamati! Vogliamo succhiare il tempo dei poveri
programmatori Perl! Altra magia Perl nera! Ancora! Ancora!

Quantum::Superpositions
A mio modesto avviso, una delle maggiori punte di esoterismo la raggiunge il modulo
Quantum::Superpositions. Che c'entra la meccanica quantistica con un tutorial sulle iterazioni in
Perl? C'entra, c'entra...
Di Quantum::Superpositions ne parla gi abbastanza larsen in questo articolo (
Perl Mongers Italia. Tutti i diritti riservati.

39 of 44

Flavio Poletti

Puoi ripetere?
http://www.perl.it/documenti/articoli/2006/07/puoi-ripetere.html
http://www.perlmonks.org/?node_id=98809 ) su Perl Monks. In pi, ne so troppo poco per poter
essere abbastanza corretto, ma mi addentro comunque...
Sostanzialmente, in meccanica quantistica l'unica cosa che si sa che non si sa niente. Un po'
come predicava Socrate. Ciascuna particella potrebbe trovarsi in uno stato o in un altro, e fino a
che non l'abbiamo in qualche modo determinato come se si trovasse virtualmente in tutti gli
stati. Un po' come se il suo stato fosse la sovrapposizione di tutti gli stati possibili.
Oggi come oggi, i computer quantici cercano di sfruttare questa propriet per implementare
operazioni implicitamente parallele. Visto che la particella si trova in tutti gli stati
contemporaneamente, possiamo fargli fare i calcoli con i vari stati contemporaneamente. Lo so,
poco chiaro anche a me, ma sono un chiacchierone con velleit di tuttologia, per cui se non fate
altre domande siamo amici, ok?
L'ovvia replica a questo punto : "ma noi non abbiamo un computer quantico!". Nemmeno io,
come la maggior parte di noi del resto. Questo non vuol dire che, per, non possiamo pensare
/come se l'avessimo/, e lasciare che il nostro computer tradizionale si occupi di emularlo
(mettendoci tanto, tanto tempo in pi...). a questo punto che Quantum::Superpositions viene
alla riscossa, perch si occupa di fare tutti i cicli necessari per emulare la contemporaneit senza
che dobbiamo farlo noi.
Il modulo mette a disposizione due funzioni che assomigliano molto ad un paio di nostre vecchie
conoscenze: any ed all, ma ne differiscono profondamente. Sostanzialmente queste due funzioni
generano un oggetto che pu essere considerato, a tutti gli effetti, un multivalore, una
sovrapposizione di stati:
my $multi_any = any( 1 .. 5 );
my $multi_all = all( 5 .. 9 );

Fin qui niente di speciale, se non che $multi_any pu essere considerato come "uno qualunque
fra 1, 2, 3, 4 e 5" mentre $multi_all pu vedersi come "5, 6, 7, 8 e 9 contemporaneamente".
Che ci facciamo ora? Cosa c'entrano con le iterazioni? Beh, il bello di queste due funzioni, e degli
oggetti che generano, risiede proprio nel fatto che nascondono le iterazioni facendoli sembrare dei
veri e propri elementi di un computer quantico. Ad esempio, possiamo scrivere molto
concisamente:
if ( all( 1 .. 5 ) <= all( 5 .. 9 ) ) {
print 'Tutti gli elementi di 1 .. 5 sono minori o '
. "uguali agli elementi di 5 .. 9\n";
}

Perl Mongers Italia. Tutti i diritti riservati.

40 of 44

Flavio Poletti

Puoi ripetere?
http://www.perl.it/documenti/articoli/2006/07/puoi-ripetere.html
Magari non efficiente, ma d'effetto! E spesso essere leggibili (e divertirsi) molto pi
importante che essere efficienti, per cui ce lo mettiamo da parte.
Ammettiamolo: non un modulo facile da usare. Eppure ci permette di scrivere funzioni in maniera
molto elegante (dalla documentazione, con un po' di modifiche per tenere conto dei casi
particolari):
#!/usr/bin/perl
use strict;
use warnings;
use Quantum::Superpositions qw( all );
for my $numero ( 1 .. 20 ) {
print $numero, "\t", (risulta_primo($numero) ? '' : 'NON '), "primo\n";
}
sub risulta_primo {
my ($n) = @_;
return 0 if $n == 1;
return 1 if $n == 2;
return $n % all(2 .. sqrt($n) + 1) != 0;
}
__END__
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

NON primo
primo
primo
NON primo
primo
NON primo
primo
NON primo
NON primo
NON primo
primo
NON primo
primo
NON primo
NON primo
NON primo
primo
NON primo
primo
NON primo

Dov' il ciclo?!? L'operatore %, che normalmente calcola il resto della divisione fra due valori, viene
qui sovraccaricato per lavorare anche con la Superposition rappresentata da
all(2..sqrt($n)+1), che rappresenta, per l'appunto, tutti i valori interi compresi fra 2 e la
radice del numero da verificare pi uno. Il sovraccarico consiste nel fatto che, implicitamente,
Perl Mongers Italia. Tutti i diritti riservati.

41 of 44

Flavio Poletti

Puoi ripetere?
http://www.perl.it/documenti/articoli/2006/07/puoi-ripetere.html
l'operazione di modulo (ossia, %) viene applicata a tutti gli elementi, generando l'oggetto costituito
da tutti i resti nella divisione di $n per ciascun elemento, ossia per ciascun valore fra 2 e la radice
di $n pi uno. Se tutti tali valori sono differenti da 0, allora il numero non divisibile per questi
divisori - quindi primo!
Ok, anche qui ne avete abbastanza, eh?

Loop Voodoo: sostituzioni arcane


Eccoci arrivati al punto di non ritorno. Abbiamo gi fatto le nostre incursioni nel lato oscuro della
Perl-forza - devo forse ricordarvi i cicli-non-cicli con un blocco nudo e redo? - ma ce ne siamo
tenuti alla larga. Abbiamo esplorato mondi strani ed esotici - Algoritm::Loops ne un esempio
lampante - ma niente che ci facesse diventare veramente cattivi con il lettore dei nostri programmi.
Leggete a vostro rischio e pericolo: per sapere che esiste, ed evitarlo!
Temerari! State ancora leggendo?!? Va bene, l'avete voluto voi: si parla dell'operatore di
sostituzione s///. Tutti gli operatori cosiddetti quote-like, ossia che lavorano similmente alle
virgolette, contengono in realt un ciclo al proprio interno. L'operatore di matching, m//, effettua
una ricerca su tutta (o quasi) la stringa; l'operatore di translitterazione, tr///, sostituisce caratteri
in tutta la stringa. Ma mentre in questi due i cicli sono immersi, per l'operatore di sostituzione
abbiamo qualche speranza di inserirci nel giro. Vediamo come.
L'operatore di sostituzione viene specificato come segue:
s/PATTERN/RIMPIAZZO/egimosx

In parole povere, cerca PATTERN e lo sostituisce con RIMPIAZZO. Le lettere minuscole finali,
egimosx, sono dei flag che consentono di cambiare il comportamento dell'espressione regolare;
ad esempio, impostando il flag i, si fa in modo che PATTERN sia considerato indifferente rispetto a
minuscole e maiuscole.
Nel nostro caso sono due le opzioni che ci interessano:
g imposta la sostituzione come globale, ossia non si limita ad agire sulla prima occorrenza di
PATTERN che viene trovata, ma su tutte quelle eventualmente presenti;
e fa s che RIMPIAZZO sia interpretato come una espressione Perl, piuttosto che come una

pura sequenza di caratteri.


Un esempio semplice di utilizzo dell'opzione g il seguente: supponiamo di avere una stringa "ciao
Perl Mongers Italia. Tutti i diritti riservati.

42 of 44

Flavio Poletti

Puoi ripetere?
http://www.perl.it/documenti/articoli/2006/07/puoi-ripetere.html
a tutti quanti", e di voler eliminare gli spazi in eccesso. Possiamo impostare una sostituzione per
cui ciascuna sequenza di uno o pi spazi viene rimpiazzata da un unico spazio, ma senza utilizzare
l'opzione abbiamo un problema:
my $stringa = "ciao
a
$stringa =~ s/ +/ /;
print $stringa, "\n";

tutti quanti";

__END__
ciao a

tutti quanti

La sostituzione stata fatta fra ciao ed a, ma non fra quest'ultimo e tutti. Aggiungendo
l'opzione g il gioco fatto:
my $stringa = "ciao
a
$stringa =~ s/ +/ /g;
print $stringa, "\n";

tutti quanti";

__END__
ciao a tutti quanti

L'utilizzo del flag e invece molto, molto pi raro, e generalmente indice del fatto che dovremmo
scrivere meglio il nostro codice. Un esempio vi far rendere conto di quanto sia bello e dannato:
my $stringa = "La radice quadrata di 2 risulta essere SQRT(2)\n";
$stringa =~ s/SQRT\((\d+)\)/sqrt($1)/e;
print $stringa;
__END__
La radice quadrata di 2 risulta essere 1.4142135623731

Gi vedo i lampi di genio nei vostri occhi. D'altronde, il sogno di ogni programmatore Perl
costruire il proprio sistema di template, giusto? Beh, questa opzione vi consente di costruirvene
uno molto grezzo - ma evitate accuratamente, ed utilizzate quello che gi esiste! Probabilmente il
Template Toolkit pu riempire il vostro vuoto e farvi utilizzare meglio il vostro tempo.
Cosa c'entra tutto questo con "fare i cicli in Perl"? Beh, un modo molto poco leggibile per scrivere
un ciclo qualunque potrebbe essere il seguente:
# Un ciclo molto bizzarro
($_ = 'x' x 5) =~ s/x/++$i; print $i, "\n"/eg;
__END__
1
2
Perl Mongers Italia. Tutti i diritti riservati.

43 of 44

Flavio Poletti

Puoi ripetere?
http://www.perl.it/documenti/articoli/2006/07/puoi-ripetere.html
3
4
5

A rimarcare che quanto scritto non si fa si volutamente evitato di dichiarare la variabile $i: il
codice risultante non gira sotto strict, quindi siete avvisati!
La prima parte costruisce una variabile temporanea $_ contenente cinque caratteri 'x'. Il numero di
'x' ci serve per poter iterare un numero uguale di volte: l'operatore di sostituzione, infatti, guarda
caso ha un PATTERN uguale a 'x', e l'opzione g attiva, per cui andr ad operare su ciascuna delle
cinque 'x' presenti in $_. In virt dell'opzione e, infine, la parte RIMPIAZZO considerata
un'espressione Perl, e viene valutata per tutte e cinque le volte: essa non fa altro che incrementare
$i e stamparla.
Ora siete pronti a farvi ridere dietro dai vostri amici!

Un po' di riferimenti
I moduli descritti sono disponibili su CPAN (http://search.cpan.org/), prendeteli! Sia su CPAN che
su Perl Monks (http://www.perlmonks.org/) potete inoltre trovare Randal L. Schwartz (merlyn),
Graham Barr (gbarr), Tassilo von Parseval (vparseval su CPAN e, probabilmente, tassilo su Perl
Monks) e Tye McQueen (tyemq in CPAN, tye in Perl Monks). Per tutto il resto, lunga vita al
manuale!
Perl Mongers Italia. Tutti i diritti riservati.

44 of 44

Potrebbero piacerti anche