Mappa Via Marconi 20, Bussolengo (VR)

Ruby Enumerables: Operation on hashes

In my last post I talked about Ruby's hash. Today I will resume and complete the argument by describing some of the things you can do to manipulate the hash values. 1. How to iterate over Hash elements The Ruby Hash class provides three different iterators: each, each_key, each_value. There is actually a fourth iterator, each_pair but it is only an alias of each. As you can expect the each iterator works as follows:

{"company" => "DevInterface", "activity"  => "Web Agency"}.each do |key, val|
p key, ": ", val
# Will print
"company: DevInterface"
"activity: Web Agency"
The other two operators, respectively, allow to iterate only on the keys or only on the values of the hash.

{"name"  => "Claudio", "role"  => "Web Developer"}.each_key do |key|
p key
# Will print

{"name"  => "Claudio", "role"  => "Web Developer"}.each_value do |val|
p val
# Will print
"Web Developer"
2. How to invert the values with the keys An operation that can be in some cases very useful it's the inversion of values with keys. The classic example is the phone book. Suppose we have an hash that contains the telephone book as follows

phone_numbers = {"DevInterface"  => "339-045-2223836", "John"  => "555-6677", "Stefano"  => "335-12345678"}
and we want to know who owns the number "555-6677". We can iterate over all the hash to find the searched value and then return the key. But what if the hash contains 1000 elements instead of 3? The alternative is to reverse the hash so numbers become the keys and names become values. The Hash class provides the invert method for this purpose:

inverted_phone_numbers = phone_numbers.invert
inverted_phone_numbers["555-6677"]  # Will return "John"
Before using the invert method, however, it's fundamental keep in mind that the hash keys are unique, but the values can be duplicated. Consequently, when reversing a hash any duplicate values will be converted into a unique key, with consequent data loss . In fact, it's not predictable which of the values will be kept and which will instead be discarded. Using the example of the phone book if we had:

phone_numbers = {"DevInterface" => "339-045-2223836", "John"  => "555-6677", "Mary"  => "555-6677", "Stefano"  => "335-12345678"}
and then perform the same operation as before

inverted_phone_numbers = phone_numbers.invert
inverted_phone_numbers["555-6677"]  # Will return "John" or "Mary"
we can not know for sure if it will return "John" or "Mary". 3. How to convert a hash into an array You can convert a hash into an array using the method to_a. The result is an array where the even elements are the keys and the odd elements are the values.

my_hash = {"a"  => 1, "b"  => 2, "c"  => 3}
my_hash.to_a   # ["a", 1, "b", 2, "c", 3]
But sometimes it is more convenient to have two separate array for keys and values. It 'can get you in the following way:

my_hash.keys.to_a     # ["a", "b", "c"]
my_hash.values.to_a   # [1, 2, 3]
Finally, using the values_at method, the hash can be extracted in an array selectively: you can select which elements to extract by passing keys to the method.

my_hash.values_at("a", "c")   # [1, 3]
Ruby also allows the reverse conversion, create a hash from an array:

my_array = [1, 2, 3, 4, 5, 6]
my_hash = Hash[*my_array]   # {1  => 2, 3  => 4, 5  => 6}
Obviously, this conversion is only possible if the array has an even number of elements. 4. How to sort an Hash A hash, as seen in previous posts, is an un-ordered structure. However, in some cases it may be needed to order the values. There is therefore a sort method. But the result of this sorting is an array. This is because Ruby, to order a hash converts it into an array of arrays and then sorts it.

beatles = {"Jonn"  => "Lennon", "Ringo"  => "Starr", "Paul"  => "McCartney", "George"  => "Harrison"}
beatles.sort  #  [["George", "Harrison"], ["Jonn", "Lennon"], ["Paul", "McCartney"], ["Ringo", "Starr"]]
5. How to merge two hashes The last topic I want to discuss today is how to merge two hashes. Suppose we want to make an unlikely merging of two historical groups, the Beatles and the Rolling Stones:

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)
The result is a third hash composed as follows:

p rolling_beatles
# {"Jonn"  => "Lennon", "Ringo" => "Starr", "Paul"  => "McCartney", "George"  => "Harrison",
"Mick"  => "Jagger", "Keith"  => "Richards", "Ronnie"  => "Wood", "Charlie"  => "Watts"}
In the case of duplicate keys, however, the merge only keeps the key of the second hash:

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} Alternatively, you can pass to the merge method a block of code to handle conflicts on the keys:

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}
As you can see the result of this merge is different from the above depending on the condition specified in the code block.