Sort hash by key, return hash in Ruby
Solution 1
In Ruby 2.1 it is simple:
h.sort.to_h
Solution 2
Note: Ruby >= 1.9.2 has an order-preserving hash: the order keys are inserted will be the order they are enumerated. The below applies to older versions or to backward-compatible code.
There is no concept of a sorted hash. So no, what you're doing isn't right.
If you want it sorted for display, return a string:
"{" + h.sort.map{|k,v| "#{k.inspect}=>#{v.inspect}"}.join(", ") + "}"
or, if you want the keys in order:
h.keys.sort
or, if you want to access the elements in order:
h.sort.map do |key,value|
# keys will arrive in order to this block, with their associated value.
end
but in summary, it makes no sense to talk about a sorted hash. From the docs, "The order in which you traverse a hash by either key or value may seem arbitrary, and will generally not be in the insertion order." So inserting keys in a specific order into the hash won't help.
Solution 3
I've always used sort_by
. You need to wrap the #sort_by
output with Hash[]
to make it output a hash, otherwise it outputs an array of arrays. Alternatively, to accomplish this you can run the #to_h
method on the array of tuples to convert them to a k=>v
structure (hash).
hsh ={"a" => 1000, "b" => 10, "c" => 200000}
Hash[hsh.sort_by{|k,v| v}] #or hsh.sort_by{|k,v| v}.to_h
There is a similar question in "How to sort a Ruby Hash by number value?".
Solution 4
Sort hash by key, return hash in Ruby
With destructuring and Hash#sort
hash.sort { |(ak, _), (bk, _)| ak <=> bk }.to_h
Enumerable#sort_by
hash.sort_by { |k, v| k }.to_h
Hash#sort with default behaviour
h = { "b" => 2, "c" => 1, "a" => 3 }
h.sort # e.g. ["a", 20] <=> ["b", 30]
hash.sort.to_h #=> { "a" => 3, "b" => 2, "c" => 1 }
Note: < Ruby 2.1
array = [["key", "value"]]
hash = Hash[array]
hash #=> {"key"=>"value"}
Note: > Ruby 2.1
[["key", "value"]].to_h #=> {"key"=>"value"}
Solution 5
You gave the best answer to yourself in the OP: Hash[h.sort]
If you crave for more possibilities, here is in-place modification of the original hash to make it sorted:
h.keys.sort.each { |k| h[k] = h.delete k }
Related videos on Youtube
Vincent
Updated on May 13, 2022Comments
-
Vincent about 2 years
Would this be the best way to sort a hash and return Hash object (instead of Array):
h = {"a"=>1, "c"=>3, "b"=>2, "d"=>4} # => {"a"=>1, "c"=>3, "b"=>2, "d"=>4} Hash[h.sort] # => {"a"=>1, "b"=>2, "c"=>3, "d"=>4}
-
PJP over 13 yearsI'm not sure there's much advantage to sorting a hash, unless you are using
each
oreach_pair
to iterate over it. Even then, I'd probably still grab the keys, sort those, then iterate over them grabbing the values as needed. That ensures the code will behave correctly on older Rubies. -
Adit Saxena almost 12 yearsMakes sense in ruby 1.9 too. I had a collection of appointments grouped by dates (as keys) coming from db and i manually sorted through ruby. Eg. { "2012-09-22": [...], "2012-09-30": [...], "2012-10-12": [...] }
-
Douglas about 9 yearsYes, I find your Hash[h.sort] process more effectiv than sorting keys then accessing again the hash trough the sorted keys.
-
PJP about 9 years"What is the fastest way to sort a Hash? will be useful.
-
Mark Thomas about 7 yearsYou've had a few years to think about your solution, are you ready to accept an answer? ;-)
-
mlabarca about 2 yearsIt's been 11 years, let's accept a solution now :P
-
-
Aaron Scruggs over 13 yearsThis is correct. I would suggest using the RBtree gem to gain ordered set functionality in ruby.
-
David over 13 yearsStarting 1.9.2 hash insert order will be preserved. See redmine.ruby-lang.org/issues/show/994
-
Peter over 13 years@David: very interesting, thanks!! Didn't realize this had changed.
-
tokland over 13 yearson the last example "h.sort" should be enough.
-
PJP over 13 years"Starting 1.9.2 hash insert order will be preserved.", and it's sweet.
-
Jo Liss over 13 yearsI agree that it's a sweet feature (thanks David for pointing it out), but I still feel a little funny about using it. I'd perhaps just use the array that comes out of
h.sort
when I care about the element ordering. -
Jo Liss over 13 years@Peter: By the way, instead of
h.sort_by(&:first)
in your examples, you can just equivalently writeh.sort
. (Docs at ruby-doc.org/core/classes/Hash.html#M002865 .) -
Jo Liss over 13 yearsRe my first comment (feeling funny): For example, relying on hash ordering would silently and unpredictably break for Ruby versions older than 1.9.2.
-
Peter over 13 years@Jo, @tokland: have amended my answer to remove the
&:first
part. -
Redoman about 11 yearsHow this answer got about 20 +1's without answering not even one of the two parts of the OP question? "1) Would that (OP example) be the best way to sort a hash, 2) and return Hash object" ? I don't envy +1's :) it's just after that reading the answer I am still left with the original questions. Also if the point is that there is not such thing as a sorted hash look at the comments for choosen answer to this question stackoverflow.com/questions/489139/…
-
boulder_ruby over 10 yearsThis is how you would do this if ruby didn't have methods like Hash#sort_by
-
stevenspiel over 10 yearsusing
sort_by
on a hash will return an array. You would need to map it out as a hash again.Hash[hsh.sort_by{|k,v| v}]
-
boulder_ruby over 10 yearsyes, the enumerator class interprets hashes as arrays I think
-
Cary Swoveland almost 10 yearsNote that
hsh.sort_by
passes tupples[k,v]
into the block, so you can writeHash[hsh.sort_by(&:first)]
. -
boulder_ruby almost 10 years@CarySwoveland wouldn't that be
:last
then because the first of each tuple would be the key, and we want to sort by value (?? not sure) -
Cary Swoveland almost 10 yearsRight, sort is on values:
hsh.sort_by(&:last).to_h => {"b"=>10, "a"=>1000, "c"=>200000}
. -
Phrogz almost 10 yearsNote that calling
to_h
is only supported in Ruby 2.1.0+ -
nitrogen about 9 yearsFor the curious, this runs slightly faster than the "keys sort" in stackoverflow.com/a/17331221/737303 on Ruby 1.9.3.
-
jitter over 8 yearsthere is a typo in the comment, correction:
sort_by{|k,v| v}.to_h)
-
Linju over 8 yearsYou can get keys and values like this too. Key of the smallest value:
*hsh[0][0]
First key value pair:Hash[*hsh[0]]
-
whitehat101 over 7 years@zachaysan but it does work:
h.sort{|a,z|a<=>z}.to_h
(tested 2.1.10, 2.3.3) -
zachaysan over 7 years@whitehat101 You are right. I had a bug (
a
is an array, not just the key). I've deleted my comment. -
Snake Sanders over 7 yearsThe link is broken
-
eremite over 7 yearsI fixed the link to point to Google in case some historian wants to research how this could have been done in the past. But anyone coming on this now should be using a more recent version of Ruby.
-
lcjury over 5 years@jj_ This question adds a lot of valuable information around the question. Yeah, it doesn't answer the question itself, but StackOverflow is not a Q&A site. This answer goes with the spirit of SO about collectively building answers.
-
silva96 about 5 yearsif not using
v
you should prefix an underscorehash.sort_by { |k, _v| k }.to_h
-
J.M. Janzen almost 5 yearsJust in case someone else is looking for a way to sort an array of hashes, this will do the trick (where h is the array):
h.map(&:sort).map(&:to_h)
. -
Kaplan Ilya over 3 yearsI wouldn't use this solution as it's not the fastest. For small hash it's fine but for couple of thousands of keys it's 10 times slower than
h.keys.sort.map { |k| [k, h[k]] }.to_h
. Not sure where is the difference on C level, but it's very clear on benchmark. Evensort_by(&:first)
is much faster. -
Mark Thomas about 3 years@KaplanIlya Likely the difference is sorting keys vs. sorting the whole hash. Still, I prefer simplicity and brevity and would reach for this solution over something more complicated unless the hash was big enough where it was a noticeable difference. And if that's the case, sorting a hash is probably not the best architectural decision anyway :)