Flavio Poletti
Te le do io le hash
http://www.perl.it/documenti/articoli/2005/10/te-le-do-io-le.html
© Perl Mongers Italia. Tutti i diritti riservati.

Perl ha, progettualmente, l'obiettivo di essere un linguaggio utile senza troppe perdite di tempo. Questo principio ispiratore lo si può vedere in azione spesso, e non è un caso che il motto di Perl sia "c'è più di un modo per farlo" (There Is More Than One Way To Do It, abbreviato in TIMTOWTDI): se hai più modi di fare una cosa, è più facile che tu riesca a farla.

Uno degli aspetti in cui il principio viene applicato è sui tipi disponibili. In generale, nei vari linguaggi troviamo i tipi a cui siamo abituati: interi, reali (a doppia precisione, ad esempio), stringhe, ecc. Sotto questo punto di vista, Perl è diverso: i principali tipi in Perl servono non tanto a distinguere cosa viene rappresentato, quanto piuttosto come. Quando parliamo di tipi principali ci riferiamo a scalari, array ed hash, tralasciando per il momento filehandle e compagnia bella.

I primi due sono relativamente facili da digerire. In fondo uno scalare tiene un valore - di qualunque natura sia. E gli array sono oggetti normalmente disponibili in altri linguaggi, anche se sono spesso pieni di insidie (come in C) e sicuramente meno usabili di quelli del Perl.

Le hash, invece, sono diverse. Non che non siano presenti in altri linguaggi: non ne conosco molti, ma sono sicuro che ci sono in PHP (coincidono sostanzialmente con gli array), e C++ le implementa nella Standard Template Library sotto il nome di map. Il problema è che sono bestie ancora non troppo note, che però danno un apporto impareggiabile alla programmazione una volta che si sa come utilizzarle.

La solita rubrica?!?

No, non la facciamo. Supponiamo invece che vogliate analizzare il log di un webserver a caso - Apache - e fare una statistica di richieste basandovi sull'indirizzo IP della sorgente. Un possibile modo potrebbe essere il seguente: manteniamo due array, uno contenente gli indirizzi IP, che cresce man mano che ne troviamo di nuovi, ed un altro contenente un contatore di quante richieste sono arrivate dal dato indirizzo. Qualcosa del genere:

per ogni riga
   ricava l'indirizzo IP sorgente
   cerca l'indirizzo nell'array indirizzi_ip
   se non lo trovi, aggiungilo alla fine e memorizza l'indice
   altrimenti memorizza l'indice e basta
   incrementa l'array contatore_richieste in corrispondenza dell'indice trovato

il che, in Perl, si tradurrebbe in qualcosa del genere (supponendo che il file sia letto da standard input, o sia stato passato a linea di comando):

#!/usr/bin/perl
use strict;
use warnings;

my (@indirizzi_ip, @contatore_richieste);

# per ogni riga
while (<>) { 
   # ricava l'indirizzo IP sorgente, dovrebbe essere all'inizio
   /^(\S+)/ or next;
   my $IP = $1;   # Risultato della cattura nel match

   # cerca l'indirizzo nell'array indirizzi_ip
   my $index;
   # Utilizziamo un ciclo tipo "C", anche se non è il massimo...
   for ($index = 0; $index < $#indirizzi_ip; ++$index) {
      if ($indirizzi_ip[$index] eq $IP)
         { last }
   }

   # Se non trovato, aggiungilo alla fine e memorizza l'indice
   if ($indice > $#indirizzi_ip) {
      $indirizzi_ip[$indice] = $IP ;
   }
   # altrimenti è già in $index

   # incrementa l'array contatore_richieste in corrispondenza
   # dell'indice trovato 
   ++$contatore_richieste[$indice];
}

Si noti che si sta utilizzando la proprietà di autovivificazione di Perl: quando un elemento di un array non esiste, ma ci si inserisce qualcosa, Perl estende l'array in modo che l'elemento possa entrarci. Senza troppe ansie da parte del programmatore.

Quando arriva un nuovo indirizzo IP dobbiamo scandire tutta la lista per constatare che non sia presente, il che è piuttosto oneroso. Il problema, però, è alla radice: la nostra formulazione dell'algoritmo prevedeva l'utilizzo di due array!

Una possibile soluzione potrebbe consistere nel memorizzare gli indirizzi in ordine alfabetico, ed operare una ricerca binaria sull'array degli indirizzi IP. Ma a chi va di implementarlo?!?

Le tre grandi virtù del programmatore sono (secondo Larry Wall) Laziness, Impatience, Hubris. Tralasciando le ultime due (impazienza ed arroganza), esercitiamo un po' la prima (pigrizia) per constatare che Perl già mette a disposizione uno strumento ideale per il problema: le hash.

In pratica, una hash è molto simile ad un array i cui indici, invece di essere interi, sono delle stringhe qualunque. Invece di essere costretti a riferirci all'elemento in posizione 4, ad esempio, possiamo dire che vogliamo accedere alla casella in posizione "pippo". L'esempio del nostro caso si semplifica allora drammaticamente: invece di tenere un doppio array, utilizziamo una sola hash in cui gli indici sono gli indirizzi IP stessi, ed i valori sono il numero di occorrenze:

#!/usr/bin/perl
use strict;
use warnings;

# Mi serve solo un contenitore! Solo che si tratta di una hash,
# quindi il simbolo da utilizzare è il "percento"
my %contatore_richieste;

# per ogni riga
while (<>) { 
   # ricava l'indirizzo IP sorgente, dovrebbe essere all'inizio
   /^(\S+)/ or next;
   my $IP = $1;   # Risultato della cattura nel match

   # Incrementa il contatore :)
   ++$contatore_richieste{$IP};
}

Obiezione: chi ci garantisce che così va più veloce? In fondo, occorre pur sempre vedere se l'elemento esiste ed eventualmente aggiungerlo, per poi incrementare il contatore relativo. Ossia, lo stesso lavoro fatto prima.

Si, il lavoro da fare è lo stesso, ma:

  • lo fa Perl - e non voi;
  • il codice che lo fa è stato controllato e ricontrollato, per cui non rischiate di inserire degli errori;
  • lo stesso codice lo fa in maniera più efficiente.

Quest'ultima affermazione è vera in due sensi. Il primo è che la funzione di ricerca/aggiunta è implementata in C, dunque si avvantaggia del fatto di essere compilata ed ottimizzata. Il secondo, e più importante, motivo è che la struttura dati utilizzata è pensata apposta per questo tipo di operazioni. Non si tratta di una coppia di array, come nella nostra (ok, mia) implementazione di prima, ma di qualcosa (le strutture hash, per l'appunto) che rendono la ricerca di un elemento drammaticamente più veloce. Un po' come il suggerimento di ordinare gli elementi dell'array indirizzi_ip per poter fare ricerche binarie, insomma.

Utilizzo di una hash

Come abbiamo visto, l'accesso agli elementi della hash si effettua utilizzando le parentesi graffe anziché le quadre come nel caso degli array. Si noti anche che i valori memorizzati sono sempre degli scalari, per cui il sigillo che abbiamo utilizzato è $:

my %hash;
my $chiave = "ciao";
my $valore = "a tutti";
$hash{$chiave} = $valore;

Ovviamente non abbiamo bisogno di utilizzare variabili:

$hash{'ciao'} = 'a tutti';

Se, inoltre, la stringa della chiave è costituita da sole lettere e numeri (oltre all'onnipresente "_"), possiamo evitare di scrivere le virgolette:

$hash{ciao} = 'a tutti';

Ovviamente non può funzionare anche per il valore!

Un'osservazione banale...

… ma doverosa: una hash non può avere chiavi duplicate, solo valori duplicati. In questo si comporta come un array: non possono esserci due posizioni "4" distinte in un array, ma due posizioni differenti possono contenere entrambe il valore 'pluto'.

Chiavi e valori

Poiché una hash può essere funzionalmente vista come una tabella che associa ad ogni chiave (stringa indice) un valore, Perl mette a disposizione delle funzioni per accedere agli array delle chiavi e dei valori:

my @indirizzi_ip = keys   %contatore_occorrenze;
my @occorrenze   = values %contatore_occorrenze;

Queste funzioni sono utili anche per sapere quanti elementi ci sono nella hash, in particolare quando sono valutate in contesto scalare:

my $numero_elementi = keys   %contatore_occorrenze;
# oppure
my $numero_elementi = values %contatore_occorrenze;

Si noti che questo non è la stessa cosa di:

my @indirizzi_ip = keys %contatore_occorrenze;

# Un array valutato in contesto scalare restituisce il numero
# di elementi nell'array
my $numero_elementi = @indirizzi_ip;

I risultati, d'accordo, sono uguali, ma nel secondo caso dovete scontare la penalità di estrarre l'intero array @indirizzi_ip, mentre la chiamata diretta a keys (o values) calcola il risultato senza perdite di tempo (e di memoria).

Sempre più... semplice

Un altro modo per impostare valori in una hash è assegnandole una lista di elementi: gli elementi dispari (il primo, il terzo, …) sono le chiavi, gli elementi pari corrispondenti sono i valori:

my %hash = ('chiave1', 12, 'chiave23', 'ciao', 'chiave e basta', 'hey!');

Per rimarcare più chiaramente questa associazione è possibile utilizzare l'operatore =>. Tale operatore, analogo alla virgola (nel senso che costruisce liste) ha una peculiarità: quanto compare alla sua sinistra subisce in generale la stessa trasformazione in stringa di cui abbiamo parlato prima riguardo all'utilizzo di chiavi costituite da sole lettere e cifre.

my %hash = (
   chiave1          => 12, 
   chiave23         => 'ciao',
   'chiave e basta' => 'hey!'
);

L'ultima chiave richiede le virgolette, è chiaro, perché contiene degli spazi. Come per le slice, anche in questo caso vige una certa legge di reciprocità: quando una hash viene passata in una sub, essa viene appiattita in una lista di chiavi e valori:

my %hash = (
   chiave1          => 12, 
   chiave23         => 'ciao', 
   'chiave e basta' => 'hey!'
);
fai_qualcosa(%hash);

sub fai_qualcosa {
   my %input = @_;  # Ora %input contiene una copia
}

Prendere più elementi insieme

Che facciamo se ci servono i valori per una lista di chiavi? Semplice, chiediamo una lista di valori utilizzando una slice:

my @valori_richiesti = @hash{'chiave1', 'chiave2', 'chiave3'};

Si noti che stiamo usando il sigillo @, perché il risultato che vogliamo ottenere è un array, non un singolo scalare.

Una slice funziona anche al contrario, ossia se vogliamo impostare un insieme di valori tutti insieme:

@hash{'k1', 'chiave2', 'hey'} = (12.34, 'ciao', 'urgh!');

Ma c'è un ma...

Abbiamo detto che una hash è una specie di array, in cui gli indici sono delle stringhe di testo e non degli interi. La similitudine, però, si ferma qui.

Come prima cosa, in un array gli elementi sono ordinati, mentre in una hash no, come potrete verificare banalmente:

#!/usr/bin/perl
use strict;
use warnings;
use Data::Dumper;

my %hash;

# L'operatore qw costruisce una lista costituita da ciascun elemento
# che trova all'interno, utilizzando gli spazi come separatori
@hash{qw( k1 k2 k3 k4 k5 )} = qw( v1 v2 v3 v4 v5 );

print Dumper(\%hash);

che stampa (l'ordine potrebbe essere diverso per voi):

$VAR1 = {
          'k5' => 'v5',
          'k2' => 'v2',
          'k1' => 'v1',
          'k3' => 'v3',
          'k4' => 'v4'
        };

L'ordine con cui vengono restituite le chiavi da keys è completamente arbitrario, come anche quello con cui vencono restituiti i valori da values. C'è però la garanzia che, nonostante tale ordine sia sconosciuto, esso risulta lo stesso per entrambe le funzioni:

#!/usr/bin/perl
use strict;
use warnings;
use Data::Dumper;

my (%hash1, %hash2);

# Inizializziamo %hash1 con un po' di valori
@hash1{qw( k1 k2 k3 k4 k5 )} = qw( v1 v2 v3 v4 v5 );

# Estraiamo i due array di chiavi e valori
my @chiavi = keys %hash1;
my @valori = values %hash1;

# Ora utilizziamoli per popolare %hash2
@hash2{@chiavi} = @valori;

print Dumper(\%hash2);

che stampa (anche qui, il vostro ordine potrebbe essere differente):

$VAR1 = {
          'k2' => 'v2',
          'k5' => 'v5',
          'k1' => 'v1',
          'k3' => 'v3',
          'k4' => 'v4'
        };

Nonostante vengano stampati in ordine differente rispetto a prima, si può vedere come ciascuna chiave sia stata associata al valore corrispondente.

Che facciamo se vogliamo ordinare una hash? Beh, la risposta è Mu - nel senso che la domanda è mal posta: una hash è fatta così, non ha un ordine. D'altro canto, possiamo accedere alla hash secondo un determinato ordine, utilizzando sort:

#!/usr/bin/perl
use strict;
use warnings;

my %hash;
@hash{qw( k1 k2 k3 k4 k5 )} = qw( v1 v2 v3 v4 v5 );

# Non utilizziamo Data::Dumper questa volta...
foreach my $key (sort keys %hash) {  # Abbiamo aggiunto sort!
   print "'$key' => '$hash{$key}'\n";
}

Il principio è semplice: non possiamo ordinare l'hash (poiché è una struttura senza ordine), ma possiamo ordinare l'array delle chiavi (che, in quanto array, è ordinabile).

Non ti ho chiesto se è vero, ti ho chiesto se esiste!

Molte volte i test sugli elementi di una hash possono contenere un'ambiguità. Consideriamo:

#!/usr/bin/perl
use strict;
use warnings;
 
my %hash = (
   ciao  => undef,
   a     => 0,
   tutti => ''
);
 
for my $chiave (qw( ciao a tutti quanti )) {
        my $veritas = ($hash{$chiave}) ? 'vero' : 'falso';
        my $definitio = (defined($hash{$chiave})) ? 'definito' : 'NON definito';
        print("$chiave e' $veritas e $definitio\n");
}

che stampa:

ciao e' falso e NON definito
a e' falso e definito
tutti e' falso e definito
quanti e' falso e NON definito

Come c'era da aspettarsi, tutti i valori sono considerati falsi. È anche ovvio che gli elementi 'a' e 'tutti' sono definiti. Il problema nasce invece per 'ciao' e 'quanti', che sono entrambi falsi e non definiti. Sembrano uguali, ma non lo sono: 'ciao' fa parte dell'hash, mentre 'quanti' no. Per questo, se ci stiamo chiedendo se un elemento è presente, abbiamo bisogno di exists:

#!/usr/bin/perl
use strict;
use warnings;
 
my %hash = (
   ciao  => undef,
   a     => 0,
   tutti => ''
);
 
for my $chiave (qw( ciao a tutti quanti )) {
        my $veritas = ($hash{$chiave}) ? 'vero' : 'falso';
        my $definitio = (defined($hash{$chiave})) ? 'definito' : 'NON definito';
        my $existentia = (exists($hash{$chiave})) ? 'esiste' : 'NON esiste';
        print("$chiave e' $veritas, $definitio e $existentia\n");
}

__END__

ciao e' falso, NON definito e esiste
a e' falso, definito e esiste
tutti e' falso, definito e esiste
quanti e' falso, NON definito e NON esiste

Riferimenti e hash anonime

Come per gli altri tipi, anche le hash consentono di essere manipolate attravarso dei riferimenti. Il sistema per ottenere un riferimento è sempre lo stesso, ossia utilizzare "\":

my %hash = (ciao => 1, 'a tutti' => 2);
my $rif_ad_hash = \%hash;

Come per gli array, inoltre, è possibile costruire hash anonime utilizzando le parentesi graffe:

my $rif_ad_hash_anonimo = {ciao => 1, 'a tutti' => 2};

Si noti come tale impostazione sia consistente con quanto accade negli array:

  • negli array si accede agli elementi mediante parentesi quadre, nelle hash mediante parentesi graffe;
  • gli array anonimi sono costruiti utilizzando parentesi quadre, le hash anonime mediante parentesi graffe.

Rimane ovviamente il fatto che le parentesi graffe sono utilizzate per tante altre cose (dove tante vuol dire TANTE), però non c'è rischio di fare confusione.

Se avete un riferimento, potete accedere all'hash nel solito modo:

my @chiavi = keys %{ $rif_ad_hash };

Questo è un esempio di ulteriore utilizzo della parentesi graffa, per inciso. Se però la situazione è come quella sopra, ossia in cui l'hash è memorizzata direttamente in una variabile scalare, potete buttare via le parentesi:

my @chiavi = keys %$rif_ad_hash;

Notate il doppio sigillo: la $ indica che rif_ad_hash contiene uno scalare, ossia il riferimento alla hash. Tale riferimento, in virtù di %, viene utilizzato per risalire alla hash vera e propria. La notazione compatta non è utilizzabile negli altri casi, ad esempio se il vostro riferimento ad hash si trova in un array:

# Prendi le chiavi dalla tredicesima hash nell'array @hashes
my @chiavi = keys %{ $hashes[12] };

Accedere ad un singolo elemento pone problemi analoghi, poiché non consente di utilizzare la notazione compatta:

my $un_valore = ${ $rif_ad_hash }{'una chiave'};

Troppo complicato! Visto che Perl è zeppo di scorciatoie, sicuramente ce ne sarà una anche in questo caso, starete dicendo. E dite giusto. Anche in questo caso, come per gli array, potete utilizzare l'operatore di dereferenziazione ->:

my $un_valore = $rif_ad_hash->{'una chiave'};
my $un_altro_valore = $hashes[12]->{'altra chiave'};

C'è di più. Se quello che precede la freccia sono parentesi graffe o quadre, infatti, potete evitare di mettere la freccia; l'ultimo caso diventa dunque:

my $un_altro_valore = $hashes[12]{'altra chiave'};

Ovviamente le hash possono contenere riferimenti ad altre hash, poiché i riferimenti sono degli scalari. In virtù di quanto abbiamo descritto, dunque, quanto segue è perfettamente lecito:

my %hash;
$hash{ciao}{a}{tutti} = 'quanti';

Che vuol dire? Semplice:

  • %hash è una hash, e fin qui non ci piove;
  • $hash{ciao} è uno scalare (e questo lo sapevamo), e contiene un riferimento ad un'altra hash anonima, poiché vi si accede utilizzando le parentesi graffe in {a};
  • stesso discorso vale per $hash{ciao}{a};
  • $hash{ciao}{a}{tutti}, infine, è un semplice scalare, che viene impostato alla stringa 'quanti'.

Strutture complesse per tutti!

Autovivificazione

Avrete sicuramente notato, nel nostro primo esempio di utilizzo dell'hash, che abbiamo scritto molto disinvoltamente:

   # Incrementa il contatore :)
   ++$contatore_richieste{$IP};

Che succede se però $IP non è ancora presente nell'hash? In questo caso Perl fa la cosa giusta, ossia crea l'elemento per voi e ve lo mette a disposizione. Per default, questo elemento è undef, ma visto che voi state insistendo a trattarlo come un numero (poiché state cercando di incrementarlo), di nuovo Perl fa la cosa giusta e considera il valore iniziale pari a 0 invece che undef. E voi vi potete concentrare sull'algoritmo invece che sui dettagli.

In generale, l'autovivificazione è abbastanza intelligente, ossia non aggiunge elementi in contesti in cui non dovrebbe. Dunque, nel seguente esempio, nonostante l'elemento relativo alla chiave 'ciao' non sia presente, ma ne sia comunque richiesto il valore, Perl è abbastanza intelligente a non infilarlo dentro:

#!/usr/bin/perl
use strict;
use warnings;
use Data::Dumper;
 
my %hash;
print("Prima: ", Dumper(\%hash));
$hash{ciao} && print("vero!\n");  # Questa non stampa
print("Dopo:  ", Dumper(\%hash));

che stampa:

Prima: $VAR1 = {};
Dopo:  $VAR1 = {};

Come si vede, l'istruzione print("vero!\n") è stata ignorata, com'era da aspettarsi poiché la chiave 'ciao' non è presente e $hash{ciao} vale undef. Tale controllo, però, non ha alterato %hash, che continua ad essere vuoto.

A volte, però, l'autovivificazione può riservare brutte sorprese; in particolare, questo accade quando si utilizzano strutture multilivello come quelle descritte in precedenza. Consideriamo il seguente semplice programma:

#!/usr/bin/perl
use strict;
use warnings;
use Data::Dumper;
 
my %hash;
print("Prima: ", Dumper(\%hash));
$hash{ciao}{a}{tutti} && print("vero!\n");  # Questa non stampa
print("Dopo:  ", Dumper(\%hash));

che stampa:

Prima: $VAR1 = {};
Dopo:  $VAR1 = {
          'ciao' => {
                      'a' => {}
                    }
        };

Oooops! Il nostro test ha alterato l'hash - com'è possibile? Osservate che, analogamente a prima, non esiste nessun elemento di chiave 'tutti' all'interno dell'hash anonimo puntato da 'a'; questo è consistente con il fatto che Perl fa la cosa giusta, e non aggiunge elementi non necessari.

D'altra parte, per arrivare a $hash{ciao}{a}{tutti}, Perl è stato costretto a creare gli elementi 'ciao' ed 'a' per poterli considerare delle hash anonime - altrimenti come avrebbe fatto ad arrivare a destinazione? Il nostro errore è stato questo dunque: con la nostra espressione abbiamo detto a Perl che per noi va bene che $hash{ciao} e $hash{ciao}{a} siano dei riferimenti ad hash, per cui Perl è stato contentissimo di crearli per noi.

In questi casi, possiamo evitare l'autovivificazione con test a più stadi:

#!/usr/bin/perl
use strict;
use warnings;
use Data::Dumper;
 
my %hash;
print("Prima: ", Dumper(\%hash));
if ($hash{ciao}                  # Verifica pezzo..
    && $hash{ciao}{a}            # per pezzo...
    && $hash{ciao}{a}{tutti}) {  # per pezzo.
        print("vero!\n");  # Questa non stampa
}
print("Dopo:  ", Dumper(\%hash));

che stampa:

Prima: $VAR1 = {};
Dopo:  $VAR1 = {};

Hash al lavoro

Le hash possono servire in tantissime occasioni, vediamone qualcuna.

Hash come insiemi

Ricordate quell'osservazione banale riguardo al fatto che le chiavi di una hash sono univoche? Questa proprietà può essere utile per utilizzare l'hash come un insieme, ossia un gruppo di elementi senza un particolare ordine ma in cui ciascun elemento compare al più una volta. Un esempio è d'obbligo.

Panoramix deve dare la pozione magica, ma prenderla due volte può dare qualche problemino ed i suoi compaesani sono un po' distratti (oltre che golosi). Utilizzando una hash, può tenere facilmente traccia di chi ha già avuto la pozione e chi no:

my %pozionati = ( Obelix => 1 );  # Obelix non può prendere la pozione!
foreach my $richiedente (@richiedenti) {
   next if $pozionati{$richiedente}; # Salta se già visto!
   dai_pozione($richiedente);
   $pozionati{$richiedente} = 1;     # Ricorda questo per la prossima volta
}

Un piccolo database

Poiché le hash possono contenere (riferimenti ad) altre hash, possiamo mantenere un piccolo database con più colonne in modo semplice:

#!/usr/bin/perl
use strict;
use warnings;
 
my %cinema;
while (<DATA>) {
        chomp();  # Rimuovi a-capo alla fine
        my ($titolo, $sala, $citta, @orari) = split /:/;
        $cinema{$citta}{$sala}{$titolo} = \@orari;
}
 
# Che sale ci sono a Milano?
print("Sale a Milano:\n");
print "\t$_\n" foreach keys %{ $cinema{Milano} };
 
# Che film fanno all'Alcazar di Roma?
print("Film all'Alcazar di Roma:\n");
print "\t$_\n" foreach keys %{ $cinema{Roma}{Alcazar} };
 
 
__DATA__
Madagascar:Alcazar:Roma:18.20:20.30
Madagascar:Trianon:Milano:19.45:22.30
Madagascar:Alcazar:Milano:16.00:18.00:20.00
Seven Swords:Alcazar:Roma:21.00:23.00

stampa:

Sale a Milano:
        Trianon
        Alcazar
Film all'Alcazar di Roma:
        Seven Swords
        Madagascar

Ordinare le chiavi in base ai valori

Ok, avete costruito la vostra hash che contiene i dati di accesso da tutti gli indirizzi IP. Come stamparla? Abbiamo visto che le chiavi (ossia, gli indirizzi IP) vengono restituiti in maniera disordinata (ossia, senza un ordine apparente) da keys, ma abbiamo anche visto che sort ci è amico. In questo caso, ordiniamo la stampa in modo che gli elementi con valore maggiore compaiano per ultimi, ossia in ordine crescente di accessi:

# I dati sono in %contatore_richieste
print("$_\t$contatore_richieste{$_}\n") foreach sort { 
      $contatore_richieste{$a} <=> $contatore_richieste{$b}
   } keys %contatore_richieste;

Invertire l'hash

L'hash, come visto, è un modo comodo per accedere ai dati (valori) associati ad una certa chiave. Può capitare, saltuariamente, di sapere il valore ma di voler conoscere quale elemento ce l'ha. Trovare questo elemento è piuttosto semplice:

my $valore_cercato = 'pippo';
my $chiave;
foreach (keys %hash) {
   if ($hash{$_} eq $valore_cercato) {
      $chiave = $_;
      last;
   }
}
# Ora $chiave è la chiave di $valore_cercato, oppure undef

Può capitare invece che tale operazione di ricerca non sia così sporadica. Che fare? Beh, si potrebbe usare un'altra hash! Ovviamente, questo pone un vincolo: come le chiavi sono tutte distinte fra loro, così anche i valori (che saranno le chiavi della nuova hash) devono essere tutti differenti fra loro:

# La rubrica ci perseguita!
my %rubrica;
# ... riempiamo la rubrica in qualche modo...

my %rubrica_inversa;
@rubrica_inversa{values %rubrica} = keys %rubrica;

Voilà!

Cala il sipario

Ancora non vi basta?!? Il riferimento ufficiale è perldata, che però non è stato ancora tradotto…

Il modulo Data::Dumper è molto utile per "vedere" cosa c'è dentro (un riferimento ad) una variabile - la lettura del manuale è vivamente consigliata.

Un'ultima cosa: il titolo di questo articolo è di larsen.