How to multiply values of same keys in hashes - ruby

Given two hashes hash1 and hash2, which may be of different size, I need to multiply the values of any recurrence of a key; the extra key-value pairs should not be taken into account.
Consider for instance the example below:
hash1 = { a: 2, b: 3, c: 4 }
hash2 = { a: 3, b: 4 }
Common keys are :a and :b (:c should not be considered). What can I do to select only :a and :b and return 2 * 3 (for :a) and 3 * 4 (for :b), that is array [6, 12]?

hash1.map { |k, v| v * hash2[k] if hash2.key? k }.compact

common_keys = hash1.keys & hash2.keys
multiply = []
common_keys.each do |key|
multiply << hash1[key] * hash2[key]
end
puts multiply

Related

How to sort by value in hash?

So I made a random number generator which is supposed to count the frequency of the numbers and display them in sorted order. I'm trying to use .sort but I can't figure out where to put it to sort the values of the hash in order. What I have so far:
MIN_VALUE = 1
count = 0
puts "Enter a number of random integers to generate"
resp = gets.to_i
p "number of integers generated is #{resp}"
puts "Now enter the maximum value the integers can be"
max_value = gets.to_i
p "max value is set to #{max_value}"
size = Array.new(resp)
while (count < resp)
int_value = (rand(MIN_VALUE..max_value))
size.push(int_value)
count = count + 1
end
puts size
freq = Hash.new(0)
size.each { |x| freq[x] += 1 }
freq.map{ |key, value| "#{key}x#{value}" }.join(',')
freq.each do |key,value|
puts "Frequency of #{key} is: #{value}"
end
Any help is greatly appreciated!
More or less the same soup, generating random numbers in an Integer#times loop:
upper_number = 10
sample_size = 100
freq = Hash.new(0) # initializing the hash with a default value of zero, for counting
sample_size.times { freq[rand((1..upper_number))] += 1 } # here the loop generating and counting
freq #=> {5=>13, 7=>10, 1=>11, 2=>13, 8=>13, 9=>6, 3=>6, 6=>9, 10=>11, 4=>8}
Then you can sort by frequencies (reverse order: -v) and by sample value (k), [-v, k]:
freq.sort_by{ |k, v| [-v, k] }.to_h #=> {2=>13, 5=>13, 8=>13, 1=>11, 10=>11, 7=>10, 6=>9, 4=>8, 3=>6, 9=>6} # for this run
freq.sum { |_, v| v} #=> 100 # of course
Suppose
arr = [4, 1, 3, 4, 2, 5, 1, 3, 4, 3, 4]
You can use the form of Hash::new that takes an argument, called its default value (which often, as here, is zero), to obtain the frequency of the elements of arr:
freq = arr.each_with_object(Hash.new(0)) { |n,h| h[n] += 1 }
#=> {4=>4, 1=>2, 3=>3, 2=>1, 5=>1}
We see that
freq[1]
#=> 2
freq[99]
#=> 0
The second result follows because freq was defined to have a default value of 0. All that means is that if freq does not have a key k, freq[k] returns zero (and that does not alter freq).
Here are solutions to two possible interpretations of your question. Both use the method Enumerable#sort_by.
Sort the unique values of arr by decreasing frequency
freq.sort_by { |_,v| -v }.map(&:first)
#=> [4, 3, 1, 2, 5]
Sort the values of arr by decreasing frequency
arr.sort_by { |n| -freq[n] }
#=> [4, 4, 4, 4, 3, 3, 3, 1, 1, 2, 5]
Replace -v and -freq[n] with v and freq[n] to sort by increasing frequency.
I've used the local variable _ to represent the keys in the first interpretation to signify that it is not used in the block calculation. This is common practice.

Is there a one liner or more efficient way to get this done?

Given a hash, for example:
hash = { "fish" => 2, "cat" => 3, "dog" => 1 }
I need to get:
A comma separated string for all the values, E.g. "2,3,1"
An integer that hold the total sum of the values, E.g. 6
My current code:
value_string = hash.map { |k,v| "#{v}"}.join(',')
sum = 0
hash.map { |k,v| sum += v}
You can do it like this:
hash.values.join(",") # => "2,3,1"
hash.values.inject(:+) # => 6
Here is a solution to compute both the values in single iteration (some sort of one liner)
r1,r2 = hash.reduce([nil, 0]){|m,(_,v)| [[m[0], v.to_s].compact.join(","),m[-1]+v]}
#=> ["2,3,1", 6]

How to merge two hashes that have same keys in ruby

I have a two hashes that should have same keys like:
a = {a: 1, b: 2, c: 3}
b = {a: 2, b: 3, c: 4}
And I want to sum up each values like this:
if a.keys == b.keys
a.values.zip(b.values).map{|a, b| a+b}
end
But this code doesn't work if the order of keys are different like b = {a: 2, c: 4, b: 3}.
How can I write the code taking into account about order of keys?
Use Hash#merge or Hash#merge!:
a = {a: 1, b: 2, c: 3}
b = {a: 2, c: 4, b: 3}
a.merge!(b) { |k, o, n| o + n }
a # => {:a=>3, :b=>5, :c=>7}
The block is called with key, old value, new value. And the return value of the block is used as a new value.
If you're using Active Support (Rails), which adds Hash#transform_values, I really like this easy-to-read solution when you have n hashes:
hashes = [hash_1, hash_2, hash_3] # any number of hashes
hashes.flat_map(&:to_a).group_by(&:first).transform_values { |x| x.sum(&:last) }

Ruby - find the key(s) of the largest value(s) of a hash

I have a hash and I want to return the key(s) (or key/value pair(s)) of the max value(s) of the hash. So, if there is only one true max, it will return that one key; however, if there are multiple key/value pairs with the same value, it will return all of these keys. How can I accomplish this in Ruby?
my_hash.max_by {|k,v| v} #only returns one key/value pair
If you want all pairs, I would do something like
max = my_hash.values.max
Hash[my_hash.select { |k, v| v == max}]
A single liner:
my_hash.reduce({}){|h,(k,v)| (h[v] ||= []) << k;h}.max
irb
> z = {:tree => 3, :two => 2, 'three' => 3}
> z.reduce({}){|h,(k,v)| (h[v] ||= []) << k;h}.max
[3, [:tree, "three"]]

Ruby Array to Histogram: How to group numbers by range?

I'm trying to group an array of integers into an hash based on where the individual values fall in a range. Basically I want to convert an array to a fixed-width histogram.
Example:
values = [1,3,4,4,4,4,4,10,12,15,18]
bin_width = 3
I need to group the array values into a range-based historgram by where they fall into a 3-unit wide bucket like so:
{'0..2'=>[1,3],'3..5'=>[4,4,4,4,4],'6..8'=>[],'9..11'=>[10]....
Is there a simple one line solution ( maybe something like values.group_by{|x| #range calc}) that would work here?
values = [1, 7, 2, 8, 2]
values.group_by { |x| x / 3 }.map { |k, vs| [(3*k..3*k+2), vs] }.to_h
#=> {0..2=>[1, 2, 2], 6..8=>[7, 8]}
If you really need the empty ranges, I don't think a clean one-liner is possible. But this should do:
grouped = values.group_by { |x| x / 3 }
min, max = grouped.keys.minmax
(min..max).map { |n| [(3*n..3*n+2), grouped.fetch(n, [])] }.to_h
#=> {0..2=>[1, 2, 2], 3..5=>[], 6..8=>[7, 8]}
I came up with a rather inefficient but quite clear solution:
ranges = 0.step(values.max, bin_width).each_cons(2).map { |s, e| Range.new(s, e, true) }
values.group_by { |v| ranges.find { |r| r.cover? v } }

Resources