How does `Hash#sort{|a, b| block}` work? - ruby

In ruby-doc I see this example:
h = { "a" => 20, "b" => 30, "c" => 10 }
h.sort {|a,b| a[1]<=>b[1]} #=> [["c", 10], ["a", 20], ["b", 30]]
Can anyone explain what a[1]<=>b[1] means? What are we comparing here? Is a is a key and b its value? Why we are comparing index 1?

a and b are both arrays of [key, value] which come from Hash#sort.
Converts hsh to a nested array of [ key, value ] arrays and sorts it, using Array#sort.
So a[1]<=>b[1] sorts the resulting pairs by the value. If it were a[0]<=>b[0] it would be sorting by the key.

Ruby doesn't have a key-value-pair or tuple datatype, so all Hash iteration methods (each, map, select, sort, …) represent hash entries as an Array with two elements, [key, value]. (In fact, most methods aren't even implemented in Hash, they are inherited from Enumerable and don't even know anything about keys and values.)

Meditate on this:
h = { "a" => 20, "b" => 30, "c" => 10 }
h.sort { |a,b| # => {"a"=>20, "b"=>30, "c"=>10}
a[1]<=>b[1]
} # => [["c", 10], ["a", 20], ["b", 30]]
For each loop of the key/value pairs in h, Ruby passes each key/value pair into the block as an array of two elements.

Related

How do I iterate over a hash by ascending value?

I'm using Ruby 2.4. I hvae a hash, whose key is a string, and whose value is a number (a positive integer). I iterate over pairs like so
my_hash.each do |key, value|
My quesiton is, how do I iterate over my hash where each pair is sorted by ascending value? That is, if my hash is
{"a" => 2, "b" => 4, "c" => 1}
The "c" => 1 pair will be iterated over first, followed by the "a" => 2, and then the "b" => 4.
You can sort the hash's key value pairs first, then iterate over it.
my_hash.sort_by { |_, value| value }.each { |key, value| puts key }
If my_hash = {"a" => 2, "b" => 4, "c" => 1}, the output is:
c
a
b
As per the suggestion, you can the use Array#last instance method to simplify.
my_hash.sort_by(&:last).each { |key, value| puts key }
Enumerable#sort_by
See Enumerable#sort_by for the documentation.
The block passed to sort_by is called with each key value pair.
The result of sort_by is a nested array.
Using the above example:
p my_hash.sort_by(&:last)
[["c", 1], ["a", 2], ["b", 4]]
In Ruby 2.7 you can numbered block parameters to reference your hash key or hash value in a shorter way. Useful if you need to e.g. convert to a string:
my_hash.sort_by { _1.to_s }.each {|key, value| puts key }

Sort hash by length of values (descending)

I'm having problems sorting a hash by the length of the array values in descending order. I have the following hash:
hash = {
"1" => [0,3],
"2" => [0,2],
"3" => [1,2,3,4],
"4" => [1,8,7,6,5],
"5" => [7,8],
"10" => [5]
}
I want to sort it to be in this order: 4,3,1,2,5,10.
hash.sort_by {|k,v| v.length}.reverse
What am I not doing right? Any ideas?
It seems you are looking for Enumerable#sort_by like this (as a note, this could be hash.sort_by {|_,v| -v.length}.to_h depending on the Ruby version. I used Hash[] because of it's compatibility).
Hash[hash.sort_by {|_,v| -v.length}]
#=>
# {
# "4"=>[1, 8, 7, 6, 5],
# "3"=>[1, 2, 3, 4],
# "1"=>[0, 3],
# "2"=>[0, 2],
# "5"=>[7, 8],
# "10"=>[5]
# }
Sorting a Hash using Enumerable#sort_by will return an associative array of [[key,value],[key,value],...] when called with a block (otherwise it returns an Enumerator). Since Hash understands associative Array structure, you can easily turn this back into a Hash by calling associative_array.to_h (Ruby >= 2.1) or Hash[associative_array] (for all Ruby Versions).
You cannot sort a hash - that might be causing your confusion. There is no "internal" ordering of elements of a hash as it appears in an array.
You can, however, iterate over a hash in a certain order, e.g.
hash.sort_by {|k,v| v.length}.reverse.each do |k, v|
puts "k = #{k}, v = #{v}"
end

sum of all elements in hash of arrays

I am given a hash with values as an array of integers. I want to add all values of same index in my result. The length of all arrays in hash values is same.
For ex,
hash = {
"A" => [1,2,3],
"B" => [1,2,3],
"C" => [1,2,3]
}
Then the resultant array i want,adding all same index elements together will be [3,6,9].
Is there any efficient way to do this in ruby ?? Or looping over the keys and array elements is the best possible solution ??
Thanks.
Here's one way:
hash.values.transpose.map { |r| r.reduce(:+) }
#=> [3,6,9]
Another would be:
values = hash.values
values.shift.zip(*values).map { |r| r.reduce(:+) }
#=> [3,6,9]
If you'd prefer using using linear algebra, you could do this:
require 'matrix'
(Matrix.row_vector([1]*hash.size)*Matrix[*hash.values]).row(0).to_a
#=> [3, 6, 9]

Sort hash by order of keys in secondary array

I have a hash:
hash = {"a" => 1, "b" =>2, "c" => 3, "d" => 4}
And I have an array:
array = ["b", "a", "d"]
I would like to create a new array that is made up of the original hash values that correspond with original hash keys that are also found in the original array while maintaining the sequence of the original array. The desired array being:
desired_array = [2, 1, 3]
The idea here is to take the word "bad", assign numbers to the alphabet, and then make an array of the numbers that correspond with "b" "a" and "d" in that order.
Since your question is a little unclear I'm assuming you want desired_array to be an array (you say you want a new array and finish the sentence off with new hash). Also in your example I'm assuming you want desired_array to be [2, 1, 4] for ['b', 'a', 'd'] and not [2, 1, 3] for ['b', 'a', 'c'].
You should just you the Enumerable#map method to create a array that will map the first array to the your desired array like so:
desired_array = array.map { |k| hash[k] }
You should familiarize yourself with the Enumerable#map method, it's quite the handy method. From the rubydocs for the method: Returns a new array with the results of running block once for every element in enum. So in this case we are iterating through array and invoking hash[k] to select the value from the hash and creating a new array with values selected by the hash. Since iteration is in order, you will maintain the original sequence.
I would use Enumerable#map followed by Enumerable#sort_by, for example:
hash = {"d" => 4, "b" =>2, "c" => 3, "a" => 1}
order = ["b", "a", "d"]
# For each key in order, create a [key, value] pair from the hash.
# (Doing it this way instead of filtering the hash.to_a is O(n) vs O(n^2) without
# an additional hash-probe mapping. It also feels more natural.)
selected_pairs = order.map {|v| [v, hash[v]]}
# For each pair create a surrogate ordering based on the `order`-index
# (The surrogate value is only computed once, not each sort-compare step.
# This is, however, an O(n^2) operation on-top of the sort.)
sorted = selected_pairs.sort_by {|p| order.find_index(p[0]) }
p sorted
# sorted =>
# [["b", 2], ["a", 1], ["d", 4]]
I've not turned the result back into a Hash, because I am of the belief that hashes should not be treated as having any sort of order, except for debugging aids. (Do keep in mind that Ruby 2 hashes are ordered-by-insertion.)
All you need is values_at:
hash.values_at *array
Enumerable methods map, each works perfect
desired_array = array.map { |k| hash[k] }
or
desired_array = array.each { |k| hash[k] }

Having trouble with the sort method

I am building a histogram based on of the amount of words in a text file. I have an array of hashes whose keys are the words and the values are the amount of times the word appears per line. I need to use the sort method on this array of hashes to sort the values in order of the most occurring word to the least. This is what my sort line looks like:
twoOfArray.sort { |k, v| v <=> k }
twoOfArray.each { |key, value| puts "#{key} occurs #{value} times" "\n"}
Full code is here. If I use the sort! method, I get an undefined method error. Does anyone know why?
I would convert your data structure (an array of hashes) into just one large hash. If you want to sort the words, there's no reason to have them in separate hashes.
Then, if your hash is something like {'the' => 5, 'and' => 23, 'beer' => 2} you can sort via:
> h = {'the' => 5, 'and' => 23, 'beer' => 2}
> a = h.sort {|a, b| b[1] <=> a[1] } # sort converts a hash into an array of arrays.
> a
#=> [['and', 23], ['the', 5], ['beer', 2]]

Resources