1

I can sort the hash below:

h = {"a"=> 20, "b"=> 30 , "c" => 25}

by using this:

h.sort { |a, b| b[1] <=> a[1] }

However, I don't understand why or how this code works. What are the values represented by a and b in |a, b| and where do they come from?

The documentation for the sort method is silent on these issues.

6
  • 2
    =/ What's your question? You just want a link to the documentation for Hash#sort? Commented Sep 25, 2015 at 16:32
  • Your title does not relate to your question. Commented Sep 25, 2015 at 16:55
  • Sorry I guess I did not make my question clear. Apparently the hash is converted to an array of [key, value] pairs when sort is called (that would help explain the use of 'a[1]' in the code). What I want to know is that is there any document that mentions this conversion of the hash to an array during the application of the sort method? Commented Sep 25, 2015 at 16:59
  • Rather than explain in a comment, forcing potential helpers to read every comment, fold that explanation into your question. The question really sounds like an XY problem where you're trying to get the values in order and access their associated keys, but you're trying to do it by sorting a hash then using each to iterate over it. That can all be short-circuited if you iterate over the returned array-of-arrays instead, which is already in the order desired. And the array-of-arrays confused you, leading to the question. Commented Sep 25, 2015 at 17:54
  • 1
    To the OPs point, it's not documented, which it should be because the conversion of the hash key/value pairs to a sub-array isn't intuitive. {}.sort #=> []? Really? Currently we all slam into that particular wall, whether we read the sort or sort_by documentation. That the resulting array-of-arrays is more useful needs to be explained so people understand the logic and design-process, rather than think it's a random decision and come up with weird work-arounds. Commented Sep 25, 2015 at 18:42

4 Answers 4

2

The Hash class inherits its sort method from the Enumerable module. The documentation for Enumerable states:

The Enumerable mixin provides collection classes with several traversal and searching methods, and with the ability to sort. The class must provide a method each, which yields successive members of the collection.

So the implementation of the methods on Enumerable rely on the each method of the class Enumerable is being included in. Specifically, it expects that each will yield members of the collection the object represents. In this case, the documentation for Hash#each says:

each {| key, value | block } → hsh

Calls block once for each key in hsh, passing the key-value pair as parameters.

Since Hash#each yields key, value for each key-value pair in the hash, the methods in Enumerable assume that key, value is a "member of the collection" in the underlying Hash (which in some sense, it is). Therefore, Enumerable#sort yields key, value for a, and a different key, value for b, because Enumerable#sort is relying on the values yielded by Hash#each in its implementation. That's where a and b are coming from in your example.

Sign up to request clarification or add additional context in comments.

4 Comments

I think this is the best explanation of what the OP wants to know. It's unfortunate, and probably an example of poor documentation, that it takes looking several places to find this out. It's definitely something that could easily be explained by an example in the docs, but NOOoooo! :-)
values yielded by Hash#each are keys and values in the hash, you can't (at least I don't think you can) apply indexing, like a[1], to them.
@OmarKhan You can. Try this: {key1: "value1", key2: "value2"}.each{|value| p value}. Ruby does a bit of array destructuring when you yield an array to a block that accepts multiple parameters. It's the same reason why you can do x, y = y, x to swap values.
@OmarKhan Or, put a different way, Hash#each actually yields a single argument, an array, to the block. |key, value| only works the way you expect because of destructuring. My guess is the docs failed to mention that little detail in order to simplify things for people who just wanted to use Hash#each directly.
1

Hash gets its sortmethod from the Enumerable module, where it is documented as returning an array.

2 Comments

Yes it mentions that it returns an array NOT that the hash is converted to an array of arrays during the application of the sort method
@OmarKhan the hash h is not converted or changed in any way. The result of sort is an array, based on the hash.
0

If you want to access the values from the hash in a particular order, then sort the hash, allow Ruby to convert it to an array of arrays and use those to extract the values with their associated keys in the desired order:

h = {"a"=> 20, "b"=> 30 , "c" => 25}
sorted_values = h.sort_by{ |k,v| v } # => [["a", 20], ["c", 25], ["b", 30]]
sorted_values.each do |k, v|
  puts "#{v} <= #{k}"
end
# >> 20 <= a
# >> 25 <= c
# >> 30 <= b

Why Ruby converts the hash key/value pairs to sub-arrays makes sense to me. The only reason I've ever figured out for sorting a hash is to access the values in a particular order and retain the mapping of keys and values, which the sub-arrays do.

Note: I'm using sort_by because it's faster when having to dig into structures or compute intermediate sorting values than sort. This particular use-case is probably not the best example since the hash is very simple, but most of my real-life examples end up having to dive in pretty deep and sort_by rules at that point.

Where is this all documented? It isn't. The documentation for Hash doesn't mention sort or sort_by which are inherited from Enumerable. Enumerable's documentation for sort and sort_by, while good, only refers to cases when an Array is being sorted. Learning that hashes are broken into key/value pairs then passed in comes from experience, it should be documented, which now can be a task you take on. Hint. Hint.

Comments

0

The documentation for Enumerable module mentions that:

The class must provide a method each, which yields successive members of the collection.

If we call each on a hash without a block it yields an Enumerator object. The successive values given by calling next on it are arrays of key-value pairs.

h = {"a"=> 10, "b"=> 20, "c"=> 30}
h_enum = h.each
p h_enum.next  #=> ["a", 10]
p h_enum.next  #=> ["b", 20]

the sort method works with these objects and hence we can use term like a[1] and b[1] for comparison

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.