Sort hash by key, return hash in Ruby

206,606

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 }
Share:
206,606

Related videos on Youtube

Vincent
Author by

Vincent

Updated on May 13, 2022

Comments

  • Vincent
    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
      PJP over 13 years
      I'm not sure there's much advantage to sorting a hash, unless you are using each or each_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
      Adit Saxena almost 12 years
      Makes 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
      Douglas about 9 years
      Yes, I find your Hash[h.sort] process more effectiv than sorting keys then accessing again the hash trough the sorted keys.
    • PJP
      PJP about 9 years
    • Mark Thomas
      Mark Thomas about 7 years
      You've had a few years to think about your solution, are you ready to accept an answer? ;-)
    • mlabarca
      mlabarca about 2 years
      It's been 11 years, let's accept a solution now :P
  • Aaron Scruggs
    Aaron Scruggs over 13 years
    This is correct. I would suggest using the RBtree gem to gain ordered set functionality in ruby.
  • David
    David over 13 years
    Starting 1.9.2 hash insert order will be preserved. See redmine.ruby-lang.org/issues/show/994
  • Peter
    Peter over 13 years
    @David: very interesting, thanks!! Didn't realize this had changed.
  • tokland
    tokland over 13 years
    on the last example "h.sort" should be enough.
  • PJP
    PJP over 13 years
    "Starting 1.9.2 hash insert order will be preserved.", and it's sweet.
  • Jo Liss
    Jo Liss over 13 years
    I 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
    Jo Liss over 13 years
    @Peter: By the way, instead of h.sort_by(&:first) in your examples, you can just equivalently write h.sort. (Docs at ruby-doc.org/core/classes/Hash.html#M002865 .)
  • Jo Liss
    Jo Liss over 13 years
    Re 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
    Peter over 13 years
    @Jo, @tokland: have amended my answer to remove the &:first part.
  • Redoman
    Redoman about 11 years
    How 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
    boulder_ruby over 10 years
    This is how you would do this if ruby didn't have methods like Hash#sort_by
  • stevenspiel
    stevenspiel over 10 years
    using 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
    boulder_ruby over 10 years
    yes, the enumerator class interprets hashes as arrays I think
  • Cary Swoveland
    Cary Swoveland almost 10 years
    Note that hsh.sort_by passes tupples [k,v] into the block, so you can write Hash[hsh.sort_by(&:first)].
  • boulder_ruby
    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
    Cary Swoveland almost 10 years
    Right, sort is on values: hsh.sort_by(&:last).to_h => {"b"=>10, "a"=>1000, "c"=>200000}.
  • Phrogz
    Phrogz almost 10 years
    Note that calling to_h is only supported in Ruby 2.1.0+
  • nitrogen
    nitrogen about 9 years
    For the curious, this runs slightly faster than the "keys sort" in stackoverflow.com/a/17331221/737303 on Ruby 1.9.3.
  • jitter
    jitter over 8 years
    there is a typo in the comment, correction: sort_by{|k,v| v}.to_h)
  • Linju
    Linju over 8 years
    You can get keys and values like this too. Key of the smallest value: *hsh[0][0] First key value pair: Hash[*hsh[0]]
  • whitehat101
    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
    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
    Snake Sanders over 7 years
    The link is broken
  • eremite
    eremite over 7 years
    I 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
    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
    silva96 about 5 years
    if not using v you should prefix an underscore hash.sort_by { |k, _v| k }.to_h
  • J.M. Janzen
    J.M. Janzen almost 5 years
    Just 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
    Kaplan Ilya over 3 years
    I 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. Even sort_by(&:first) is much faster.
  • Mark Thomas
    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 :)