ChatGPT解决这个技术问题 Extra ChatGPT

Sort hash by key, return hash in Ruby

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}
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.
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": [...] }
Yes, I find your Hash[h.sort] process more effectiv than sorting keys then accessing again the hash trough the sorted keys.
You've had a few years to think about your solution, are you ready to accept an answer? ;-)

M
Mark Thomas

In Ruby 2.1 it is simple:

h.sort.to_h

@zachaysan but it does work: h.sort{|a,z|a<=>z}.to_h (tested 2.1.10, 2.3.3)
@whitehat101 You are right. I had a bug (a is an array, not just the key). I've deleted my comment.
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).
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.
@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 :)
M
Metaxis

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.


This is correct. I would suggest using the RBtree gem to gain ordered set functionality in ruby.
Starting 1.9.2 hash insert order will be preserved. See redmine.ruby-lang.org/issues/show/994
"Starting 1.9.2 hash insert order will be preserved.", and it's sweet.
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.
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/…
C
Community

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?".


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}]
yes, the enumerator class interprets hashes as arrays I think
Right, sort is on values: hsh.sort_by(&:last).to_h => {"b"=>10, "a"=>1000, "c"=>200000}.
Note that calling to_h is only supported in Ruby 2.1.0+
there is a typo in the comment, correction: sort_by{|k,v| v}.to_h)
M
Moriarty

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"}

if not using v you should prefix an underscore hash.sort_by { |k, _v| k }.to_h
B
Boris Stitnicky

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 }

For the curious, this runs slightly faster than the "keys sort" in stackoverflow.com/a/17331221/737303 on Ruby 1.9.3.
f
fl00r

No, it is not (Ruby 1.9.x)

require 'benchmark'

h = {"a"=>1, "c"=>3, "b"=>2, "d"=>4}
many = 100_000

Benchmark.bm do |b|
  GC.start

  b.report("hash sort") do
    many.times do
      Hash[h.sort]
    end
  end

  GC.start

  b.report("keys sort") do
    many.times do
      nh = {}
      h.keys.sort.each do |k|
        nh[k] = h[k]
      end
    end
  end
end

       user     system      total        real
hash sort  0.400000   0.000000   0.400000 (  0.405588)
keys sort  0.250000   0.010000   0.260000 (  0.260303)

For big hashes difference will grow up to 10x and more


e
eremite

ActiveSupport::OrderedHash is another option if you don't want to use ruby 1.9.2 or roll your own workarounds.


The link is broken
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.
Y
Yurii Stefaniuk

You can use sort method and then convert array back to hash with to_h method

h = { "a" => 1, "c" => 3, "b" => 2, "d" => 4 }
h.sort.to_h
# => { "a" => 1, "b" => 2, "c" => 3, "d" => 4 }

b
beornborn
@ordered = {}
@unordered.keys.sort.each do |key|
  @ordered[key] = @unordered[key]
end

This is how you would do this if ruby didn't have methods like Hash#sort_by
G
Gabriel Mesquita

I had the same problem ( I had to sort my equipments by their name ) and i solved like this:

<% @equipments.sort.each do |name, quantity| %>
...
<% end %>

@equipments is a hash that I build on my model and return on my controller. If you call .sort it will sort the hash based on it's key value.


s
spume

I borrowed Boris Stitnicky's inspired solution to patch an in-place sort! method into Hash:

def sort!
  keys.sort!.each { |k| store k, delete(k) }
  self
end

C
Community

I liked the solution in the earlier post.

I made a mini-class, called it class AlphabeticalHash. It also has a method called ap, which accepts one argument, a Hash, as input: ap variable. Akin to pp (pp variable)

But it will (try and) print in alphabetical list (its keys). Dunno if anyone else wants to use this, it's available as a gem, you can install it as such: gem install alphabetical_hash

For me, this is simple enough. If others need more functionality, let me know, I'll include it into the gem.

EDIT: Credit goes to Peter, who gave me the idea. :)