Il più famoso framework basato sul linguaggio Ruby, Ruby on Rails, fa largo uso delle hash. E visto che nel nostro blog parliamo principalmente di sviluppo con Ruby on Rails mi pare giusto approfondire alcuni costrutti del linguaggio largamente utilizzati dal nostro framework di riferimento.
In questo e nei prossimi post voglio presentare in breve le caratteristiche e le proprietà di alcuni dei costrutti più utilizzati, cominciando appunto dalle hash.
Le hash, note anche come array associativi o dizionari sono strutture dati molto flessibili che permettono di associare un valore ad una chiave che non sia necessariamente un intero, come accade invece con gli array.
Un'altra caratteristica che distingue le hash dagli array è che tipicamente una hash è una struttura non ordinata.
1. Creazione ed inizializzazione
Una hash viene creata in modo molto semplice con il metodo [].
Naturalmente una hash può essere anche inizializzata con alcuni valori in fase di creazione.
Vediamo alcuni esempi:
a = Hash.[] # Crea una nuova hash vuota
# Crea una nuova hash inizializzata
b = Hash.[]("name", "Claudio", "age", "30")
c = Hash.[]("name" => "Claudio", "age" => "30")
d = Hash["name", "Claudio", "age", "30"]
e = Hash["name" => "Claudio", "age" => "30"]
f = {"name", "Claudio", "age", "30"}
g = {"name" => "Claudio", "age" => "30"}
Tutte le hash da ag risulteranno popolate alla stessa maniera. Personalmente prediligo la sintassi dell'ultima riga, ma il risultato è assolutamente identico per ognuna delle forme presentate.
E' possibile anche inizializzare una hash con un valore di default da ritornare al posto di nil nel caso si tenti di accedere ad una chiave inesistente.
Per fare ciò si utilizza il metodo new.
a = Hash.new # Crea una nuova hash vuota
# Crea una nuova hash con default value = -1
b = Hash.new(-1)
b["test"] # -1
b.inspect # {}
Il valore di default può essere impostato anche dopo la creazione della hash utilizzando il metodo default=.
a = Hash.[] # Crea una nuova hash vuota
a["test"] # nil
a.default= "no value"
a["test"] # "no value"
a.default # "no value"
Esiste inoltre un metodo fetch che ritorna un'eccezione IndexError nel caso si acceda ad una chiave inesistente.
Il metodo fetch può essere invocato anche con un secondo parametro che agisce come valore di default oppure con un blocco di codice che genera il valore di default a runtime in caso la chiave non esista.
a = {"name" => "Claudio", "age" => 30, "city" => "Verona"}
a.fetch("surname") # IndexError
a.fetch("surname", "Marai") # "Marai"
a.fetch("age", "Marai") # 30
a.fetch("name"){|x| x.upcase} # "Claudio"
a.fetch("surname"){|x| x.upcase} # "SURNAME"
2. Inserimento, modifica, cancellazione
Per aggiungere nuovi valori, accedere o modificare i valori presenti, Hash mette a disposizione i metodi [] e []= che si possono utilizzare come segue:
a = {"name" => "Claudio"}
a["name"] # "Claudio"
a["age"]= 30
a["age"] # 30
a["age"]= 31
a["age"] # 31
La cancellazione di uno specifico valore può essere effettuata con il metodo delete passando come parametro la chiave della coppia chiave-valore da rimuovere.
a = {"name" => "Claudio", "age" => 30}
a.delete("name")
a["name"] # nil
a["age"] # 30
Il metodo clear invece svuota completamente la hash rimuovendo tutte le coppie chiave-valore.
Il metodo delete_if permette di cancellare tutte le coppie che soddisfano una determinata condizione specificata in un blocco.
a = {"name" => "Claudio", "age" => 30, "city" => "Verona"}
a.delete_if{|key, value| key.include? "c"}
# a => {"name" => "Claudio", "age" => 30}
Infine il metodo reject agisce come delete_if ma su una copia della hash che viene ritornata dal metodo stesso.
Fondamentalmente eseguire a.reject equivale ad come eseguire a.dup.delete_if.
Questo conclude le operazioni di base sulle hash. Nel prossimo post descriverò come iterare sui valori di una hash, convertirla in array o fare altre operazioni quali merge e sort.