Do average on hashes - ruby

lets say i got 3 hashes:
hash1={
a => 1,
b => 2,
c => 4
}
hash2={
b => 1,
c => 3
}
hash3={
a => 2,
b => 1,
c => 3,
d => 4
}
I want to average stuff to a new hash according to keys, so new hash will be
result={a=>1.5,b=>4/3,c=>10/3,d=>4}
meaning if key not exists in one hash we don't count it as 0.
is there an elegant way?

result = {}
[hash1, hash2, hash3].each{|h| h.each{|k, v| (result[k] ||= []).push(v)}}
result.each{|k, v| result[k] = v.inject(:+).to_f/v.length}
result # =>
# {
# a => 1.5,
# b => 1.3333333333333333,
# c => 3.3333333333333335,
# d => 4.0
# }

Related

hash.merge with block to add up sub-hash keys?

I have read something about hash merge with a block and this is working fine for simple, non-nested hashes in plain ruby. The following code results in {1=>2, 2=>4, 4=>6} as expected:
a = {1 => 1, 2 => 2, 4 => 3}
b = {1 => 1, 2 => 2, 4 => 3}
a.merge(b) { |key, value_a, value_b | value_a + value_b }
But the merge is not working for a nested hash structure, I get a NoMethodError (undefined method '+' for {1=>1, 2=>2}:Hash)
a = { "2018" => {1 => 1, 2 => 2, 4 => 3} }
b = { "2019" => {1 => 1, 2 => 2, 4 => 3} }
c = a.merge(b) { |key, value_a, value_b | value_a + value_b }
I have read about each_with_object and I am unsure how to use it. Is there a smart way to accomplish the merge of the values of the sub-hash? What do you think is the easiest way?
You can use Hash#deep_merge from active support to do this.
require 'active_support/all'
a = { k1: { k2: 1 } }
b = { k1: { k2: 2 } }
a.deep_merge(b) { |k, v1, v2| v1 + v2 }
# => { l1: { k2: 3 } }
Nested Hash, nested Hash#merge?
I changed the key of b to "2018"
a = { "2018" => {1 => 1, 2 => 2, 4 => 3} }
b = { "2018" => {1 => 1, 2 => 2, 4 => 3} }
c = a.merge(b) { |k, v1, v2| v1.merge(v2) { |kk, aa, bb | aa + bb } }
#=> {"2018"=>{1=>2, 2=>4, 4=>6}}
For your original values:
a = { "2018" => {1 => 1, 2 => 2, 4 => 3} }
b = { "2019" => {1 => 1, 2 => 2, 4 => 3} }
The result is
#=> {"2018"=>{1=>1, 2=>2, 4=>3}, "2019"=>{1=>1, 2=>2, 4=>3}}

map_values() for Ruby hashes?

I miss a Hash method in Ruby to transform/map only the values of the hash.
h = { 1 => [9,2,3,4], 2 => [6], 3 => [5,7,1] }
h.map_values { |v| v.size }
#=> { 1 => 4, 2 => 1, 3 => 3 }
How do you archive this in Ruby?
Update: I'm looking for an implementation of map_values().
# more examples
h.map_values { |v| v.reduce(0, :+) }
#=> { 1 => 18, 2 => 6, 3 => 13 }
h.map_values(&:min)
#=> { 1 => 2, 2 => 6, 3 => 1 }
Ruby 2.4 introduced the methods Hash#transform_values and Hash#transform_values! with the desired behavoir.
h = { 1=>[9, 2, 3, 4], 2=>[6], 3=>[5, 7, 1] }
h.transform_values { |e| e.size }
#=> {1=>4, 2=>1, 3=>3}
You can monkey-patch the hash class, like this
class Hash
def map_values
map { |k, v|
[k, yield(v)]
}.to_h
end
end
p ({1 => [1,1,1,1], 2 => [2], 3 => [3,3,3]}.map_values { |e| e.size })
You can also use Hash#update for this:
h = { 1 => [9, 2, 3, 4], 2 => [6], 3 => [5, 7, 1] }
h.update(h) { |_, v| v.size }
#=> { 1 => 4, 2 => 1, 3 => 3 }
It replaces all values that have duplicate keys in one hash with that of another, or, if a block is given, with the result of calling the block. You can pass the original hash as the argument to ensure all values are replaced.
Note that this modifies the hash in place! If you want to preserve the original hash, dup it first:
h.dup.update(h) { |_, v| v.size }
#=> { 1 => 4, 2 => 1, 3 => 3 }
h
#=> { 1 => [9, 2, 3, 4], 2 => [6], 3 => [5, 7, 1] }
This will do the trick for you
h = { 1 => [1,1,1,1], 2 => [2], 3 => [3,3,3] }
h.map {|k,v| [k, v.size] }.to_h
No map, just each
h = { 1 => [1,1,1,1], 2 => [2], 3 => [3,3,3] }
h.each{|k,v| h[k] = v.size}
You can achieve this by:
h.map { |a, b| [a, b.size] }.to_h
#=> {1=>4, 2=>1, 3=>3}
Here is one more way to achieve it:
h = { 1 => [1,1,1,1], 2 => [2], 3 => [3,3,3] }
p h.keys.zip(h.values.map(&:size)).to_h
#=> {1=>4, 2=>1, 3=>3}
There's an implementation of this method in the DeepEnumerable library: https://github.com/dgopstein/deep_enumerable/
It's called shallow_map_values:
>> require 'deep_enumerable'
>> h = { 1 => [9,2,3,4], 2 => [6], 3 => [5,7,1] }
>> h.shallow_map_values { |v| v.size }
=> {1=>4, 2=>1, 3=>3}

Combine hash keys and values if keys are identical except for case

Let's say I have a Ruby Hash where at least two keys are identical, except for their case, for instance:
{ 'Foo' => 1, 'foo' => 2, 'bar' => 3 }
Is there a way I can combine like keys (except for their case) such that the resulting Hash might look like this?
{ 'foo' => 3, 'bar' => 3 }
Thank you!
You can build a new hash:
new_hash = Hash.new(0)
old_hash.each_pair { |k, v| new_hash[k.downcase] += v }
You can use inject to loop all the hash items and build a new hash.
hash = { 'Foo' => 1, 'foo' => 2, 'bar' => 3 }
hash.inject({}) do |result, (key, value)|
key = key.downcase
result[key] = result[key] ? result[key] + value : value
result
end
Here is one more way of doing this
h = { 'Foo' => 1, 'foo' => 2, 'bar' => 3 }
p h.collect{|k, v| {k.downcase => v}}.reduce { |a, v| a.update(v) {|k, o, n| o + n } }
#=> {"foo"=>3, "bar"=>3}
h = { 'Foo' => 1, 'foo' => 2, 'bar' => 3 }
h.each_with_object({}) { |(k,v),g| g[k.downcase] = g[k.downcase].to_i + v }
#=> {"foo"=>3, "bar"=>3}
This makes use of the fact that if g does not have a key e, g[e] on the right side will equal nil and nil.to_i #=> 0. On the other hand, if g has a key e, h[e].to_i will equal h[e].
Another way:
h.each_with_object({}) { |(k,v),g| g.update(k.downcase=>v) { |_,o,v| o+v } }
#=> {"foo"=>3, "bar"=>3}

ruby use array tvalues to index nested hash of hash [duplicate]

This question already has answers here:
What is the most ruby-ish way of accessing nested hash values at arbitrary depths? [duplicate]
(4 answers)
How to avoid NoMethodError for missing elements in nested hashes, without repeated nil checks?
(16 answers)
Closed 7 years ago.
In Ruby, I want to do something like this,
I have a hash of hash built like this.
h = {1 => {2 => {3 => "three"}},'a' => { 'b' => { 'c' => "basd"}}}
=> {"a"=>{"b"=>{"c"=>"basd"}}, 1=>{2=>{3=>"three"}}}
If I have an array with values like this.
a = [1, 2, 3]
I want to have a method which will use the array values to index nested keys in my hash and return the value pointed by last key (as guided by previous array/keys)
for eg.
getHashValue([1,2,3]) should return "three" => h[1][2][3]
if a = ['a','b', 'c'] then the return value should be basd.
How to get this done?
And then there's:
keys.inject(hash, :fetch)
or for earlier Ruby versions:
keys.inject(hash) {|h, k| h[k]}
If you did want to use recursion, a more Rubyesque approach would be:
def get_value(obj, keys)
keys.empty? ? obj : get_value(obj[keys[0]], keys[1..-1])
end
Simple recursion
def getValue(hash, keys, i = 0)
if i + 1 < keys.length
getValue(hash[keys[i]], keys, i + 1)
else
hash[keys[i]]
end
end
getValue(h, [1,2,3]) => "three"
getValue(h, ['a','b','c']) => "basd"
Ruby 2.3.0 introduced a new method called dig on both Hash and Array that solves this problem entirely.
It returns nil if an element is missing at any level of nesting.
h1 = {}
h2 = { a: {} }
h3 = { a: { b: {} } }
h4 = { a: { b: { c: 100 } } }
h1.dig(:a, :b, :c) # => nil
h2.dig(:a, :b, :c) # => nil
h3.dig(:a, :b, :c) # => nil
h4.dig(:a, :b, :c) # => 100
h = {1 => {2 => {3 => "three"}},'a' => { 'b' => { 'c' => "basd"}}}
a = ['a','b', 'c']
a.inject(h, :[]) # => "basd"
h = {1 => {2 => {3 => "three"}},'a' => { 'b' => { 'c' => "basd"}}}
a = [1, 2, 3]
a.inject(h, :[]) # => "three"

Ruby merge hashes putting keys into csv string

Is there a clever way to achieve the following in Ruby?
hash1 = { "a" => 1, "b" => 2, "d" => 3}
hash2 = { "a" => 4, "b" => 5, "c" => 7}
hash3 = { "a" => 4, "d" => 7, "e" => 9}
puts hash1.csvMerge(hash2).csvMerge(hash3)
with the output being:
{ "a" => "1,4,4",
"b" => "2,5,0",
"c" => "0,7,0",
"d" => "3,0,7",
"e" => "0,0,9" }
What I'm trying to do is merge a bunch of Ruby hashes, in practice I have over a dozen, into a single hash where the values are combined into a comma separated string.
hash1.merge(hash2){|key, oldval, newval| [oldval,newval].join(",")}
=> {"a"=>"1,4", "b"=>"2,5", "d"=>3, "c"=>7}
hashes = [hash1, hash2, hash3]
keys = hashes.inject({}){|hh, h| hh = hh.merge(h); hh}.keys # the set of all keys #
default_hash = keys.inject({}){|d, k| d[k] = 0; d} # hash with value 0 for all keys #
complemented_hashes = hashes.map{|h| default_hash.merge(h)} # missing values filled in #
p Hash[complemented_hashes.map{|h| h.to_a}.flatten(1).group_by{|k, v| k}.
map{|k, v| [k, v.map{|k, v| v}.join(",")]}]
I dont know if this has performance issues
hashes = [hash1, hash2, hash3]
hash_with_all_keys = {}
hashes.each{|hash| hash_with_all_keys.merge!(hash)}
keys = hash_with_all_keys.keys
result_hash_mapping = keys.map do |key|
value = hashes.map{|hash| hash[key].to_i}.join(",")
[key, value]
end
result_hash = Hash[result_hash_mapping]
I could have replaced lines 2 to 4 with keys = hashes.inject({}){|merge_hash, hash| merge_hash.merge(hash)}.keys but I find it hard to read.
hash1 = { "a" => 1, "b" => 2, "d" => 3}
hash2 = { "a" => 4, "b" => 5, "c" => 7}
hash3 = { "a" => 4, "d" => 7, "e" => 9}
hashes = [hash1, hash2, hash3]
hashes.each{|h| h.each{|k,v| h[k] = v.to_s }}
res = hashes.inject{|m,h| m.merge(h){|k,old,new| "#{old},#{new}" }} #merge and glue
p res
#=> {"a"=>"1,4,4", "b"=>"2,5", "d"=>"3,7", "c"=>"7", "e"=>"9"}
Functional approach:
hashes = [hash1, hash2, hash3]
result = Hash[hashes.flat_map(&:keys).uniq.map do |key|
[key, hashes.map { |h| h[key] || 0 }.join(",")]
end]
#=> {"a"=>"1,4,4", "b"=>"2,5,0", "c"=>"0,7,0", "d"=>"3,0,7", "e"=>"0,0,9"}

Resources