Ruby Enumerables: Operazioni con le hash

Nel mio ultimo post ho parlato delle hash di Ruby.
Oggi riprendo e completo l’argomento descrivendo alcune delle operazioni che si possono eseguire sulle hash per manipolarne i valori.
1. Accedere ai valori iterativamente
La classe Hash di ruby mette a disposizione tre diversi iteratori: each, each_key, each_value.
Esiste in realtà un quarto iteratore, each_pair che però è solo un alias di each.
Come ci si può aspettare l’iteratore each funziona come segue:

1
2
3
4
5
6
{"company" => "DevInterface", "activity" => "Web Agency"}.each do |key, val|
  p key, ": ", val
end
# Will print
"company: DevInterface"
"activity: Web Agency"

Gli altri due operatori permettono rispettivamente di iterare solo sulle chiavi o solo sui valori della hash.

1
2
3
4
5
6
7
8
9
10
11
12
{"name" => "Claudio", "role" => "Web Developer"}.each_key do |key|
  p key
end
# Will print
"name"
"role"
{"name" => "Claudio", "role" => "Web Developer"}.each_value do |val|
  p val
end
# Will print
"Claudio"
"Web Developer"

2. Invertire i valori con le chiavi
Un’operazione che può essere molto utile in certi casi è l’inversione dei valori con le chiavi.
L’esempio classico è la rubrica telefonica.
Supponiamo di avere una hash che contiene la rubrica telefonica come segue

1
phone_numbers = {"DevInterface" => "339-045-2223836", "John" => "555-6677", "Stefano" => "335-12345678"}

e di voler sapere a chi appartiene il numero “555-6677″.
Potremmo iterare su tutta la hash fino a trovare il valore cercato e poi ritornare la chiave. Ma se la hash invece di 3 elementi ne contenesse 1000 questa operazione diventerebbe molto lenta.
L’alternativa è invertire la nostra hash in modo che i numeri diventino le chiavi ed i nomi i valori.
La class Hash mette a disposizione per questo scopo il metodo invert:

1
2
inverted_phone_numbers = phone_numbers.invert
inverted_phone_numbers["555-6677"]  # Will return "John"

Prima di utilizzare il metodo invert tuttavia, è necessario tenere presente che le hash hanno chiavi uniche, ma i valori possono essere duplicati.
Di conseguenza, quando si inverte una hash eventuali valori duplicati saranno convertiti in un unica chiave, con conseguente perdita di informazioni.
Di fatto, non è predicibile quale dei valori verrà tenuto e quali verranno invece scartati.
Riprendendo l’esempio della rubrica se avessimo:

1
phone_numbers = {"DevInterface" => "339-045-2223836", "John" => "555-6677", "Mary" => "555-6677", "Stefano" => "335-12345678"}

ed eseguissimo la stessa operazione di prima

1
2
inverted_phone_numbers = phone_numbers.invert
inverted_phone_numbers["555-6677"]  # Will return "John" or "Mary"

non potremo sapere con certezza se ci verrà restituito “John” oppure “Mary”.
3. Convertire un hash in un array
E’ possibile convertire una hash in un array utilizzando il metodo to_a.
Il risultato sarà un array in cui gli elementi pari corrispondono alle chiavi ed i dispari ai valori della hash di partenza.

1
2
my_hash = {"a" => 1, "b" => 2, "c" => 3}
my_hash.to_a   # ["a", 1, "b", 2, "c", 3]

A volte però è più comodo avere due array separati per chiavi e valori. E’ possibile ottenerli nel seguente modo:

1
2
my_hash.keys.to_a     # ["a", "b", "c"]
my_hash.values.to_a   # [1, 2, 3]

Infine, utilizzando il metodo values_at della classe Hash è possibile estrarre in un array solo alcuni valori specifici, determinati dalle chiavi passate al metodo:

1
my_hash.values_at("a", "c")   # [1, 3]

Ruby permette anche la conversione inversa, ovvero creare una hash a partire da un array:

1
2
my_array = [1, 2, 3, 4, 5, 6]
my_hash = Hash[*my_array]   # {1 => 2, 3 => 4, 5 => 6}

Questa conversione è possibile solo se l’array ha un numero pari di elementi.
4. Ordinamento
Una hash, come visto nel post precedente, è una struttura non ordinata.
Tuttavia in alcuni casi può rendersi necesario ordinarne i valori. Esiste quindi un metodo sort anche per le hash il cui risultato però è un array.
Questo perchè Ruby, per ordinare una hash la converte in un array di array e poi lo ordina.

1
2
beatles = {"Jonn" => "Lennon", "Ringo" => "Starr", "Paul" => "McCartney", "George" => "Harrison"}
beatles.sort  #  [["George", "Harrison"], ["Jonn", "Lennon"], ["Paul", "McCartney"], ["Ringo", "Starr"]]

5. Merge di due Hash
L’ultimo argomento che voglio trattare oggi è il merge di due hash.
Supponiamo di voler fare un improbabile merge tra due storici gruppi, i Beatles ed i Rolling Stones:

1
2
3
beatles = {"Jonn" => "Lennon", "Ringo" => "Starr", "Paul" => "McCartney", "George" => "Harrison"}
rolling_stones = {"Mick" => "Jagger", "Keith" => "Richards", "Ronnie" => "Wood", "Charlie" => "Watts"}
rolling_beatles = beatles.merge(rolling_stones)

Il risultato è una terza hash composta come segue:

1
2
3
p rolling_beatles
# {"Jonn" => "Lennon", "Ringo" => "Starr", "Paul" => "McCartney", "George" => "Harrison",
 "Mick" => "Jagger", "Keith" => "Richards", "Ronnie" => "Wood", "Charlie" => "Watts"}

Nel caso di chiavi duplicate però, il merge mantiene solo la chiave della seconda hash:

1
2
3
a = {"a" => 1, "b" => 2, "c" =>3}
b = {"a" => 5, "d" => 7, "e" =>9}
c = a.merge(b)  # {"a" => 5, "b" => 2, "c" =>3, "d" => 7, "e" => 9}

In alternativa, è possibile passare al metodo merge un blocco di codice per gestire i conflitti sulle chiavi

1
2
3
4
a = {"a" => 1, "b" => 2, "c" =>3}
b = {"a" => 5, "d" => 7, "e" =>9}
c = a.merge(b) {|key, old_val, new_val| old_val < new_val ? old_val : new_val}
 # {"a" => 1, "b" => 2, "c" =>3, "d" => 7, "e" => 9}

Come potete vedere il risultato di questo merge è differente dal precedente in base alla condizione specificata nel blocco di codice.

Tags: , , , ,


About Claudio

Claudio Marai is a co-founder of DevInterface.

After graduating in Computer Science has contributed to develop complex web applications based on Java/J2EE and desktop applications with the. NET framework for the Ministry of Justice and ultimately for the banking ambit.

The passion for web in recent years has led him to be interested in more modern frameworks such as Ruby on Rails and Django, and to a development approach based on agile methodologies such as eXtreme Programming and SCRUM.

About DevInterface

We are an information and communication technology agency. Our mission is to provide web application development, design services and communication strategies. We specialize in building web applications with modern and efficient frameworks.

Related Post

9 Responses to “Ruby Enumerables: Operazioni con le hash”

  1. Kris scrive:

    Looks like the syntax highlighting is escaping the ‘greater than’ characters.

  2. Claudio scrive:

    Hi Kris, thanks for the tip.
    It seems like there was a problem with one of the plugins we use.
    It should now be back all right.

  3. Jim scrive:

    Cool, I didn’t realize that merges of duplicate hashes used the value from the second hash, good to know.

  4. Jakub Godawa scrive:

    I would suggest instead of invert – because of performance:

    1
    2
    phone_numbers.key('555-6677')
    # John
  5. [...] This post was mentioned on Twitter by Ruby Brasil, DevInterface. DevInterface said: Ruby Enumerables: Operation on hashesRuby Enumerables: Operazioni con le hash http://f.ast.ly/VJw7T [...]

  6. Michel Pigassou scrive:

    Hi.
    Could be worth it to mention that in Ruby 1.9, Hashes have an order and can be sorted directly, ie. hash.sort will give a sorted Hash (instead of an Array in Ruby 1.8).

  7. Millisami scrive:

    There is a typo in the last exampe.

    c = a.merge(b) {|key, old_val, new_val| old_val < new_val ? old_val : new_val}

  8. Claudio scrive:

    Thank you Millisami,
    I’ve fixed it!

  9. [...] http://blog.devinterface.com/2011/02/ruby-enumerables-operation-on-hashes/ Share this:TwitterFacebookLike this:LikeBe the first to like this. Posted on June 2, 2012 by Jyothi. This entry was posted in Important. Bookmark the permalink. [...]

Leave a Reply

Insert code beetween <code lang="ruby"> and </code>

Copyright 2012 DevInterface s.n.c.

DevInterface Blog is proudly powered by WordPress