Ruby - Grab Most Pairs From a Hash - ruby

hsh = { one: 1, two: 2, three: 3, four: [1, 3, 1, 4] }
How do i grab all key/value pairs from the hash except for where the value is an array? Or, how do i grab all key/value pairs from the hash except for where the key is :four?

It is not clear what you mean by "grab". To get a hash, do the following:
hsh.reject{|k, v| v.kind_of?(Array)}
hsh.reject{|k, v| k == :four}

Related

How do I find the keys and the value in a hash with the minimum value, only among certain keys?

Using Ruby 2.4. I have a hash with keys and values that are both numbers (integers). Given a set of keys, how do I find the entry (both key and value) that has the lowest value? If I wanted to find the minimum value I could do
my_hash.select{|k, v| my_selected_keys.include?(k) }.values.min_by(&:last)
But this only gets me the value, not both the key and the value. Also note that values in my hash are not necessarily unique.
Is this what you're looking for?
It gets all keys that have the minimum value. I've split out and named many of the intermediate objects for clarity:
hash = { 4 => 5, 2 => 9, 3 => 1, 8 => 5 }
selected = Set[2, 4, 8]
hash_subset = hash.slice(*selected)
keys_by_value = hash_subset.group_by(&:last).each_value { |group| group.map!(&:first) }
min_value, keys_with_min_val = keys_by_value.min_by(&:first)
# => [5, [4, 8]]
and the obligatory ruby oneliner:
hash.slice(*selected).group_by(&:pop).each_value(&:flatten!).min_by(&:first)
You can map the selected keys sel_keys:
hash = {1 => 2, 3 => 1, 4 => 5, 5 => 1}
sel_keys = [3, 4]
sel_keys.map { |k| [k,hash[k]] }.min_by(&:last) #=> [3, 1]
Caveat: This returns only the first key found with the min value.
You could sort the hash by the value and then take the first element
my_hash.select{|k, _| my_selected_keys.include?(k) }.sort_by{|_, v| v}.first
Here is an alternative solution
hash.reduce([nil, Float::INFINITY]) { |acc, pair| acc[1] < pair[1] ? acc : pair }

Hash of hashes from an array

From an array:
this = [1, 2, 3, 4, 5]
I am trying to create a hash of hashes:
{{num: 1}, {num: 2}, {num: 3}, {num: 4}, {num: 5}}
But I'm getting an empty hash:
Hash.new(this.each do |num| Hash.new(num: num) end)
# => {}
What am I doing wrong?
First, your desired result in your question doesn't make sense since you're using the Hash {} syntax, but there are no keys. It seems as though you want your result to be an array of hashes.
Second, you're confusing each with map. each simply iterates through an array, passing each item to the block. The return value of arr.each is just arr. map, on the other hand, returns a new array based on the return value of the block:
[1, 2, 3, 4, 5].map { |item| { num: item } }
You are setting the default value (furthermore with a block that does not do anything meaningful) without setting any key-value pairs.

How to preserve alphabetical order of keys when sorting a hash by the value

beginner here. My first question. Go easy on me.
Given the following hash:
pets_ages = {"Eric" => 6, "Harry" => 3, "Georgie" => 12, "Bogart" => 4, "Poly" => 4,
"Annie" => 1, "Dot" => 3}
and running the following method:
pets_ages.sort {|x, y| x[1] <=> y[1]}.to_h
the following is returned:
{
"Annie" => 1,
"Dot" => 3,
"Harry" => 3,
"Poly" => 4,
"Bogart" => 4,
"Eric" => 6,
"Georgie" => 12
}
You will notice the hash is nicely sorted by the value, as intended. What I'd like to change is the ordering of the keys, so that they remain alphabetical in the case of a tie. Notice "Dot" and "Harry" are correct in that regard, but for some reason "Poly" and "Bogart" are not. My theory is that it is automatically sorting the keys by length in the case of a tie, and not alphabetically. How can I change that?
In many languages, Hashes/Dicts aren't ordered, because of how the are implemented under the covers. Ruby 1.9+ is nice enough to guarantee ordering.
You can do this in a single pass - Ruby allows you to sort by arbitrary criteria.
# Given
pets_ages = {"Eric" => 6, "Harry" => 3, "Georgie" => 12, "Bogart" => 4, "Poly" => 4, "Annie" => 1, "Dot" => 3}
# Sort pets by the critera of "If names are equal, sort by name, else, sort by age"
pets_ages.sort {|(n1, a1), (n2, a2)| a1 == a2 ? n1 <=> n2 : a1 <=> a2 }.to_h
# => {"Annie"=>1, "Dot"=>3, "Harry"=>3, "Bogart"=>4, "Poly"=>4, "Eric"=>6, "Georgie"=>12}
Hash#sort will return an array of [k, v] pairs, but those k, v pairs can be sorted by any criteria you want in a single pass. Once we have the sorted pairs, we turn it back into a Hash with Array#to_h (Ruby 2.1+), or you can use Hash[sorted_result] in earlier versions, as Beartech points out.
You could get as complex as you want in the sort block; if you're familiar with Javascript sorting, Ruby actually works the same here. The <=> method returns -1, 0, or 1 depending on how the objects compare to each other. #sort just expects one of those return values, which tells it how the two given values relate to each other. You don't even have to use <=> at all if you don't want to - something like this is equivalent to the more compact form:
pets_ages.sort do |a, b|
if a[1] == b[1]
if a[0] > b[0]
1
elsif a[0] < b[0]
-1
else
0
end
else
if a[1] > b[1]
1
elsif a[1] < b[1]
-1
end
end
end
As you can see, as long as you always return something in the set (-1 0 1), your sort function can do whatever you want, so you can compose them however you'd like. However, such verbose forms are practically never necessary in Ruby, because of the super handy <=> operator!
As Stefan points out, though, you have a BIG shortcut here: Array#<=> is nice enough to compare each entry between the compared arrays. This means that we can do something like:
pets_ages.sort {|a, b| a.reverse <=> b.reverse }.to_h
This takes each [k, v] pair, reverses it into [v, k], and uses Array#<=> to compare it. Since you need to perform this same operation on each [k, v] pair compared, you can shortcut it even further with #sort_by
pets_ages.sort_by {|k, v| [v, k] }.to_h
What this does is for each hash entry, it passes the key and value to the block, and the return result of the block is what is used to compare this [k, v] pair to other entries. Since comparing [v, k] to another [v, k] pair will give us the result we want, we just return an array consisting of [v, k], which sort_by collects and sorts the original [k, v] pairs by.
As Philip pointed out, hashes were not meant to preserve order, though I think in the latest Ruby they might. But let's say they don't. Here's an array based solution that could then be re-hashed:
Edit here it is in a one-liner:
new_pets_ages = Hash[pets_ages.sort.sort_by {|a| a[1]}]
previous answer:
pets_ages = {"Eric" => 6, "Harry" => 3, "Georgie" => 12, "Bogart" => 4, "Poly" => 4,
"Annie" => 1, "Dot" => 3}
arr = pets_ages.sort
# [["Annie", 1], ["Bogart", 4], ["Dot", 3], ["Eric", 6], ["Georgie", 12],
# ["Harry", 3], ["Poly", 4]]
new_arr = arr.sort_by {|a| a[1]}
#[["Annie", 1], ["Dot", 3], ["Harry", 3], ["Bogart", 4], ["Poly", 4], ["Eric", 6],
# ["Georgie", 12]]
And finally to get a hash back:
h = Hash[new_arr]
#{"Annie"=>1, "Dot"=>3, "Harry"=>3, "Bogart"=>4, "Poly"=>4, "Eric"=>6,
# "Georgie"=>12}
So when we sort a hash, it gives us an array of arrays with the items sorted by the original keys. Then we sort that array of arrays by the second value of each, and since it's a lazy sort, it only shifts them if need be. Then we can send it back to a hash. I'm sure there's a trick way to do a two-pass sort in one line but this seems pretty simple.
As you already know few methods of ruby for sorting that you have used. So I would not explain it you in detail rather keep it very simple one liner for you. Here is your answer:
pets_ages.sort.sort_by{|pets| pets[1]}.to_h
Thanks

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

How to find out the number of times an element in Array1 is present in Array2

What is the best way to accomplish this in Ruby? Array1 contains few numbers Array2 contains unsorted numbers. We want to find out how often each element of Array1 shows up in Array2.
Example:
Array1 = [0,1,2,3]
Array2 = [0,0,0,3,3,3,2,1,0,3,6,1,3]
Result = {"0"=>4, "1"=>2, "2"=>1, "3"=>5}
Is there a better optimal way to do this than:
picking each element of Array1
iterating over Array2
incrementing a counter each time elements match
Example just shows few number but I want to find out best way to do this for a very large array set.
You can use group_by to count the items in array2:
irb(main):001:0> array1 = [0,1,2,3]
=> [0, 1, 2, 3]
irb(main):002:0> array2 = [0,0,0,3,3,3,2,1,0,3,6,1,3]
=> [0, 0, 0, 3, 3, 3, 2, 1, 0, 3, 6, 1, 3]
irb(main):003:0> h = Hash[array2.group_by { |x| x }.map { |k, v| [k, v.size] }]
=> {0=>4, 3=>5, 2=>1, 1=>2, 6=>1}
If you want, you can then extract the sub-hash with the keys from array1 (but I don't think this is really necessary):
irb(main):004:0> h.select { |k,_| array1.include?(k) }
=> {0=>4, 3=>5, 2=>1, 1=>2}
If the numbers in array are not very large then you can make an array with size as the greatest element of any of the array. Then iterate the array once and increment the position by 1. i.e.
Suppose the largest element in array 2 is 10
declare an array with length 10 i.e a[10] and initialize the array with 0
Now suppose you find 1 in array2 then increment a[1]
If you find 4 in array2 then increment a[4] and so on
Then again iterate over array1 once and match the corresponding element
I have not considered the language as Ruby but if you have got the idea then it is fairly easy to implement in any language.
Complexity: O(n)
a = [0,1,2,3]
b = [0,0,0,3,3,3,2,1,0,3,6,1,3]
a.inject({}){|h,i| h[i] = b.count(i); h}
#=> {0=>4, 1=>2, 2=>1, 3=>5}
or
Hash[a.map{|i| [i,b.count(i)]}]
#=> {0=>4, 1=>2, 2=>1, 3=>5}

Resources