I am trying to sort a Hash alphabetically by key, but I can\'t seem to find a way to do it without creating my own Sorting class. I found the code below to sort by value if it\'
You can create a new empty hash to hold the sorted hash data. Iterate through the returned array and load the data into the new hash to hold the sorted hash data.
temp = {}
temp["ninjas"]=36
temp["pirates"]=12
temp["cheese"]=222
temp = temp.sort_by { |key, val| key }
temp_sorted = {}
temp.each { |sub_arr| temp_sorted[sub_arr[0]] = sub_arr[1] }
temp = temp_sorted
temp now equals {"cheese"=>222, "ninjas"=>36, "pirates"=>12}
Ruby's Hash remembers its insertion order now days, but earlier Rubies < v1.9 don't. But, don't bother sorting a hash as there is no advantage to doing so because basically a Hash is a random-access structure. That means the elements are all accessible at any time and it won't make a difference whether one is first or last, you can access it just the same.
That's unlike an Array which acts like a sequential/text file or a chain or a queue and you have to access it linearly by iterating over it, which, at that point, the order of the elements makes a big difference.
So, with a Hash, get the keys, sort them, and either iterate over the list of keys or use values_at
to retrieve all the values at once. For instance:
hash = {
'z' => 9,
'a' => 1
}
sorted_keys = hash.keys.sort # => ["a", "z"]
sorted_keys.each do |k|
puts hash[k]
end
# >> 1
# >> 9
hash.values_at(*sorted_keys) # => [1, 9]
Some languages won't even let you sort the hash, and accessing it via a sorted list of keys is the only way to extract the elements in an order, so it's probably a good idea to not get in the habit of relying on order of the key/value pairs, and instead rely on the keys.
sorted_by_key = Hash[original_hash.sort]
will create a new Hash by inserting the key/values of original_hash
alphabetically by key. Ruby 2.x hashes remember their insertion order, so this new hash will appear sorted by key if you enumerate it or output it.
If you insert more elements in a non-alphabetical order, this won't hold of course.
Also, this assumes the original hash keys are all sortable/comparable.
In addition to Neil Slater's answer, which uses the Hash#sort_by
method (which is nice and concise when outputting comparable values in the block)...
irb(main):001:0> h = { a: 0, b: 5, c: 3, d: 2, e: 3, f:1 }
=> {:a=>0, :b=>5, :c=>3, :d=>2, :e=>3, :f=>1}
irb(main):002:0> h.sort_by { |pair| pair[1] }.to_h
=> {:a=>0, :f=>1, :d=>2, :c=>3, :e=>3, :b=>5}
...or the reverse variant...
irb(main):003:0> h.sort_by { |pair| pair[1] }.reverse.to_h
=> {:b=>5, :e=>3, :c=>3, :d=>2, :f=>1, :a=>0}
...there is also the option to use the Array#sort
method which allows you to define your own comparison rules (e.g. this sorts by value ascending, but then by key descending on equal values):
irb(main):004:0> h.to_a.sort { |one, other| (one[1] == other[1]) ? other[0] <=> one[0] : one[1] <=> other[1] }.to_h
=> {:a=>0, :f=>1, :d=>2, :e=>3, :c=>3, :b=>5}
This last option is a bit less concise, but can be more flexible (e.g. custom logic to deal with a mixture of types).
Assuming you want the output to be a hash which will iterate through keys in sorted order, then you are nearly there. Hash#sort_by
returns an Array
of Array
s, and the inner arrays are all two elements.
Ruby's Hash
has a constructor that can consume this output.
Try this:
temp = Hash[ temp.sort_by { |key, val| key } ]
or more concisely
temp = temp.sort_by { |key| key }.to_h
If your hash has mixed key types, this will not work (Ruby will not automatically sort between String
s and Symbol
s for instance) and you will get an error message like comparison of Symbol with String failed (ArgumentError). If so, you could alter the above to
temp = Hash[ temp.sort_by { |key, val| key.to_s } ]
to work around the issue. However be warned that the keys will still retain their original types which could cause problems with assumptions in later code. Also, most built-in classes support a .to_s
method, so you may get unwanted results from that (such as unexpected sort order for numeric keys, or other unexpected types).
You could, in addition, convert the keys to Strings
with something like this:
temp = Hash[ temp.map { |key, val| [key.to_s, val] }.sort ]
. . . although this approach would lose information about the type of the original key making it impossible to refer back to the original data reliably.