How to Sort a Hash in Ruby
Let's say you have the following hash of people to ages:
people = { :fred => 23, :joan => 18, :pete => 54 }
Now, what if we want to "sort" the hash into age order? We can't. At least, not exactly. Hashes are not meant to be in a certain order (though they are in Ruby 1.9) as they're a data structure where one thing merely relates to another thing. The relationships themselves are not elements of data in themselves.
We can, however, build other data structures that represent sorted versions of the data within a hash. An array, for example. Let's say we want to get a list of the ages in order:
people.values.sort # => [18, 23, 54]
This gives us the values but you might want to have both the values and their associated keys in a particular order.
Enumerable To The Rescue!
Luckily, Hash has the Enumerable
module mixed in which provides us with methods like sort
and sort_by
. Let's give them a go:
people.sort # NoMethodError: undefined method `< =>' for :joan:Symbol
The sort
method uses the <=>
comparison operator to put things into order. Symbols don't have a <=>
comparison operator (by default - you could create one!) so it's a fail. We can, however, use sort_by
to get where we want to go:
people.sort_by { |name, age| age } # => [[:joan, 18], [:fred, 23], [:pete, 54]]
In this situation we're using sort_by
to sort by a specific collection - the values (ages, in our case). Since integers (FixNum
objects, in this case) can be compared with <=>
, we're good to go. We get a nested array back with one element per hash element in order to preserve the 'ordering'. You can then use Array
or other Enumerable
methods to work with the result (tip: each
is a good place to start!)
A More Complex Situation
Let's make our hash more complex:
people = { :fred => { :name => "Fred", :age => 23 }, :joan => { :name => "Joan", :age => 18 }, :pete => { :name => "Pete", :age => 54 } }
This time we not only have a hash - we have a hash filled with other hashes. What if we want to sort by values within the nested hashes?
It's much the same as before, except we address the inner hash during the sort:
people.sort_by { |k, v| v[:age] } # => [[:joan, {:name=>"Joan", :age=>18}], [:fred, {:name=>"Fred", :age=>23}], [:pete, {:name=>"Pete", :age=>54}]]
In this way, we could even sort by the :name
key, if we so chose.