Ruby language

La classe hash                

Le istanze della classe hash, di seguito diremo un hash, non so mai come metterla tenendo la dizione inglese, sono concettualmente non distanti dagli array, tanto che a volte sono indicati come associative array (altrove, in altri linguaggi, si chiamano mappe o dizionari); anche in questo caso infatti si tratta di una sequenza di oggetti indicizzata, solo che gli indici non necessariamente sono interi ma possono essere una gran varietà di oggetti tanto che gli hash sono solitamente indicate come sequenze di coppie costituite da una chiave (l'indice) e da un valore (l'oggetto associato all'indice). La chiave ed il valore possono essere oggetti qualsiasi.
La definizione, da un punto di vista formale è la seguente:

nomehash = { chiave-1 => valore-1, chiave-2 => valore-2, ... , chiave-n => valore-n }

Il legame tra chiave e valore è pertanto fornito da quel simbolo composto => (fino alla versione 1.8 erano ammesse le virgole che successivamente sono state deprecate e poi escluse) che troviamo fra i due; ogni coppia è tenuta separata dalle altre tramite una virgola. Quindi in pratica:

hash01 = {"uno" => 1, "due" => 2, "dieci" => 10}

è un esempio concreto che mostra come creare un hash ex-novo. Non è necessario che le chiavi siano tutte dello stesso tipo, potete avere un mix di tipologie di chiavi e va bene lo stesso per l'interprete, forse un po' meno per la chiarezza del programma; la libertà è bella ma non bisogna abusarne.

 hash1 = {"uno" => 1, 2 => 2}

Qui, ad esempio, abbiamo come chiavi, una stringa e un intero. Molto usati come chiavi sono i symbols, che abbiamo incontrato nel paragrafo dedicato alla stringhe, principalmente per questioni prestazionali (ecco quindi un caso in cui i simboli sembrano e sono decisamente preferibili rispetto alle stringhe e ad altri oggetti); adoperarli in congiunzione con gli hash quando possibile è sempre una buona scelta in Ruby. Il loro uso può essere formalmente impostato in due modi:

irb(main):001:0> h1 = {:x => 1, :y => 2}
=> {:x=>1, :y=>2}
irb(main):002:0> h2 = {x: 1, y: 2}
=> {:x=>1, :y=>2}
irb(main):003:0> h1.class
=> Hash
irb(main):004:0> h2.class
=> Hash
irb(main):005:0>

Ovvero nel primo caso con una scrittura se volete più "classica", nel secondo usando una forma abbreviata, con i due punti posti dopo il nome del symbol e forse più comoda.

Per inciso è possibile creare un hash vuoto tramite il metodo new:

h02 = Hash.new

da notare l'uso della maiuscola nella keyword Hash dovuta al fatto che ci rivolgiamo alla classe Hash nativa nel type system di Ruby. A seguito di questa definizione si possono poi aggiungere gli elementi che vogliamo specificando la chiave e quindi il relativo valore:

irb(main):023:0> h02 = Hash.new()
=> {}
irb(main):024:0> h02["1"] = 1
=> 1
irb(main):025:0> h02
=> {"1"=>1}
irb(main):026:0> h02["2"] = 2
=> 2
irb(main):027:0> h02
=> {"1"=>1, "2"=>2}

 Ecco, nell'ultima riga evidenziato il risultato, un hash con "1" e "2" come chiavi mentre 1 e 2 sono i valori. Tra poco parleremo dell'operatore [].

Quello che comunque è bene capire è che, nonostante la loro somiglianza logica con gli array, un hash non è quindi regolato da un ordinamento ma semplicemente da un serie di legami tra valore e chiave.

In fase di definizione delle chiavi attenzione ai duplicati: Ruby non perdona e l'ultimo valore inserito sostituisce, facendolo perdere, il primo avente la stessa chiave. Cioè:

irb(main):031:0> h03 = {0 => 0, 1 => 1, 0 => 3}
=> {0=>3, 1=>1}

Ecco qua che il valore 0 iniziale se ne è andato....

Iniziamo a lavorare con questa interessante classe, dotata anch'essa di un gran numero di proprietà ovviamente tutte ben spiegate sul sito ufficiale iniziando dalla più semplice delle proprietà ovvero size che ha lo scopo di farci capire quante voci ci sono all'interno di un dato hash:

irb(main):001:0> dict = {1 => "uno", 2 => "due", 3 => "tre"}
=> {1=>"uno", 2=>"due", 3=>"tre"}
irb(main):002:0> dict.size
=> 3

Il risultato è 3 in quanto vi sono 3 coppie chiave - valore.

Detto questo è ovviamente importante come accedere ad un singolo elemento. Come avrete forse intuito a seguito del primo esempio si usa il solito operatore [] che racchiude al suo interno una chiave. Rifacendoci al codice visto qui sopra, ad esempio abbiamo:

puts dict[1]
uno

Per inciso grazie a [] possiamo anche sostituire un valore esistente con altro:

nomehash[chiave-esistente] = nuovo-valore

L'accesso che si ha con le parentesi quadre permette anche la scrittura:

irb(main):006:0> dict[1] = "unouno"
=> "unouno"
irb(main):007:0> dict
=> {1=>"unouno", 2=>"due", 3=>"tre"}

Avendo di fronte un hash può essere interessante conosce tutte le chiavi in esso contenute. Se infatti abbiamo a che fare con un array non è difficile capire che le chiavi vanno da 0 ad n-1 se n è il numero degli elementi. Però in un hash, come abbiamo visto, è diverso. C'è un modo per estrarre tutte le chiavi? Ovviamente si. L'istruzione è keys.

irb(main):008:0> dict.keys
=> [1, 2, 3]

Il risultato è un array che contiene tutte le chiavi. Come avrete forse già intuito c'è un equivalente per i valori ovvero l'istruzione values che restituisce un array contenente i soli valori:

irb(main):009:0> dict.values
=> ["uno", "due", "tre"]

E' possibile avere la chiave alla quale è legato un certo valore, conoscendolo a priori, usando l'istruzione key(valore):

irb(main):010:0> dict.key("due")
=> 2

Ruby ci mette a disposizione anche le istruzioni per verificare se una certa chiave o un certo valore sono presenti: has_key? e has_value? che, tra le altre cose, sono istruzioni decisamente efficienti per quel tipo di ricerca. Molto meglio che iterare affannosamente su tutti gli elementi cercandone uno.

irb(main):018:0> h01.has_key?(3)
=> false
irb(main):020:0> h01.has_value?("b")
=> true

Vi domanderete ora come aggiungere o togliere elementi da un hash. Il discorso è molto semplice, abbiamo già visto ad inizio paragrafo, presentando il metodo new, come aggiungere elementi in modo naturale. Comunque esiste anche un metodo forse più chiaro in termini di leggibilità ovvero store(key, value) mentre per cancellare un elemento ecco a disposizione delete(key).

irb(main):011:0> h01 = {1 => 'a', 2 => 'b', 3 => 'c'}
=> {1=>"a", 2=>"b", 3=>"c"}
irb(main):012:0> h01.store(4, 'd')
=> "d"
irb(main):013:0> h01
=> {1=>"a", 2=>"b", 3=>"c", 4=>"d"}
irb(main):014:0> h01.delete(3)
=> "c"
irb(main):015:0> h01
=> {1=>"a", 2=>"b", 4=>"d"}

In pratica creiamo un hash con 3 elementi, ne aggiungiamo un quarto e cancelliamo il terzo. L'esempio direi che è molto semplice. Se volete ripulire completamente un hash esiste il metodo clear (nell'esempio sarebbe h01.clear)

Anche in questo caso dovremmo vedere come iterare sugli elementi di un hash ma per le iterazioni c'è un apposito paragrafo.

Chiudiamo questo paragrafo con una curiosità, ovvero il fatto che è possibile usare un hash per sostituire una operazione di selezione; lo vedremo quando parleremo di queste.