How do I iterate over a hash by ascending value? - ruby

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 }

Related

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

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

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.

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]]

Each_pair analog for array (zipping 2 arrays)

There is each_pair method allows getting pairs of hash on every loop iteration:
{ 1 => "a", 2 => "b" }.each_pair do |key, value|
# do something with #{key} and #{value}
end
How can index of current array element could be known on every loop iteration?
array.each do |element|
# what is element index in array?
end
There is a boring solution using some incrementing iterator. But that iterator
should be assigned before the loop and should be manually incremented on every
iteration. It's obviously boring.
It will be great if there is a some way to zip some array with 1.. array and
get array of tuples like ["b", "d", "e"] → [(1,"b"), (2,"d"), (3,"e")] and
than pattern matched each element of the pair in| |` statement.
So, finally, what I am looking for is some function f, that:
f(["a"]) do |index, element|
# index == 1, element == "a"
end
You can loop over an array and get the current index by using Enumerable::each_with_index
Correct me if I'm wrong, but I'm assuming that you want an array consisting of sub-arrays with the originall arrays index and value?
a= ["b", "d", "e"]
a.enum_with_index.map {|ind, val| [ind, val]
=> [[0, "b"], [1, "d"], [2, "e"]]

Resources