How to merge hash of arrays? - ruby

I have an array:
[
{a => 1, b => { c => 1, d => 1}},
{a => 1, b => { c => 1, d => 2}},
{a => 1, b => { c => 2, d => 2}},
{a => 2, b => { c => 1, d => 1}},
]
I want to change it to this:
[
{a => 1, b => [{ c => 1, d => [1, 2]}, { c => 2, d => [2]}]},
{a => 2, b=> [ { c=> 1, d => [1] } ]}
]
Rules/Requirements:
Hashes of same value of a go to one hash
b should be an array of {c => , d =>}
d should be an array
d with same value of c go to same array

Here is a solution. It is very explicit, so it would not generalize to other hash structures.
hashes = [
{:a => 1, :b => { :c => 1, :d => 1}},
{:a => 1, :b => { :c => 1, :d => 2}},
{:a => 1, :b => { :c => 2, :d => 2}},
{:a => 2, :b => { :c => 1, :d => 1}},
]
a_values = {}
hashes.each do |hash|
a_value = hash[:a]
a_values[a_value] ||= {}
c_value = hash[:b][:c]
a_values[a_value][c_value] ||= { :c => c_value, :d => [] }
d_value = hash[:b][:d]
a_values[a_value][c_value][:d].push(d_value)
end
# Now aggregate the results
results = a_values.map do |a_value, c_hashes|
b_arr = c_hashes.map { |c_value, c_hash| c_hash }
{ :a => a_value, :b => b_arr }
end
And here is the output:
[
{:a=>1, :b=>[{:c=>1, :d=>[1, 2]}, {:c=>2, :d=>[2]}]},
{:a=>2, :b=>[{:c=>1, :d=>[1]}]}
]

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

Add element to a nested hash in ruby

I have a hash:
a = { 21 => { 3 => {:x => 5, :y => 6}}}
I want to add another value to the key '21' so that the hash looks like this:
a = { 21 => { 3 => {:x => 5, :y => 6}, 4 => {:x => 8, :y => 7}}}
How can I do that?
You want to add an key-value pair to a hash (a[21]). a[21] will give you the inner hash object.
a = { 21 => { 3 => {:x => 5, :y => 6}}}
a[21]
# => {3=>{:x=>5, :y=>6}}
Associating key, values to the inner hash will solve your problem.
a[21][4] = {:x => 8, :y => 7}
a
# => {21=>{3=>{:x=>5, :y=>6}, 4=>{:x=>8, :y=>7}}}
Another way is:
a[21].update({ 4=>{:x => 8, :y => 7} })
a #=> {21=>{3=>{:x=>5, :y=>6}, 4=>{:x=>8, :y=>7}}}

Collect values from an array of hashes

I have a data structure in the following format:
data_hash = [
{ price: 1, count: 3 },
{ price: 2, count: 3 },
{ price: 3, count: 3 }
]
Is there an efficient way to get the values of :price as an array like [1,2,3]?
First, if you are using ruby < 1.9:
array = [
{:price => 1, :count => 3},
{:price => 2, :count => 3},
{:price => 3, :count => 3}
]
Then to get what you need:
array.map{|x| x[:price]}
There is a closed question that redirects here asking about handing map a Symbol to derive a key. This can be done using an Enumerable as a middle-man:
array = [
{:price => 1, :count => 3},
{:price => 2, :count => 3},
{:price => 3, :count => 3}
]
array.each.with_object(:price).map(&:[])
#=> [1, 2, 3]
Beyond being slightly more verbose and more difficult to understand, it also slower.
Benchmark.bm do |b|
b.report { 10000.times { array.map{|x| x[:price] } } }
b.report { 10000.times { array.each.with_object(:price).map(&:[]) } }
end
# user system total real
# 0.004816 0.000005 0.004821 ( 0.004816)
# 0.015723 0.000606 0.016329 ( 0.016334)

Sum 2 hashes attributes with the same key

I have 2 hashes, for example:
{'a' => 30, 'b' => 14}
{'a' => 4, 'b' => 23, 'c' => 7}
where a, b and c are objects. How can I sum those hashes' keys to get a new hash like:
{'a' => 34, 'b' => 37, 'c' => 7}
a_hash = {'a' => 30, 'b' => 14}
b_hash = {'a' => 4, 'b' => 23, 'c' => 7}
a_hash.merge(b_hash){ |k, a_value, b_value| a_value + b_value }
=> {"a"=>34, "b"=>37, "c"=>7}
b_hash.merge(a_hash){ |k, b_value, a_value| a_value + b_value }
=> {"a"=>34, "b"=>37, "c"=>7}
If some one looking to add more than 2 hashes, use this
#sample array with any number of hashes
sample_arr = [{:a=>2, :b=>4, :c=>8, :d=>20, :e=>5},
{:a=>1, :b=>2, :c=>4, :d=>10, :e=>5, :r=>7},
{:a=>1, :b=>2, :c=>4, :d=>10},
{:a=>2, :b=>4, :c=>8, :d=>20, :e=>5},
{:a=>1, :b=>2, :c=>4, :d=>10, :e=>5, :r=>7},
{:a=>1, :b=>2, :c=>4, :d=>10}]
sample_arr.inject { |acc, next_obj| acc.merge(next_obj) { |key,arg1,arg2| arg1+arg2 } }
# => {:a=>8, :b=>16, :c=>32, :d=>80, :e=>20, :r=>14}
In case of heterogeneous hash (containing both String and Number). For adding only integers.
#resultant_visit_hash = arr.inject { |acc, next_obj| acc.merge(next_obj) { |key,arg1,arg2| arg1+arg2 if (arg1.class == Integer && arg2.class == Integer) } }
Code is self explanatory.

Ruby: This "Double dimensional hash" requires processing

I have this:
h = { 1 => { 1 => {:a => "x", :b => "y", :c => "z"},
2 => {:a => "xx", :b => "yy", :c => "zz"}
},
2 => { 1 => {:a => "p", :b => "q", :c => "r"},
2 => {:a => "pp", :b => "qq", :c => "rr"}
}
}
I want to get this:
result = { 1 => { 1 => {:a => "x"},
2 => {:a => "xx"}
},
2 => { 1 => {:a => "p"},
2 => {:a => "pp"}
}
}
What would be a nice way of doing this ?
A single example can't really define your structure. For example, are hashes always 3 levels deep with hashes to be pruned at the level 3?
You can start with:
h.each{|k1,v1| v1.each{|k2, v2| v2.delete_if{|k3,v3| k3 != :a}}}
(Should really be a comment, but the code's hard to read that way)
If you're removing from the innermost hash all keys except :a, why not assign the value part of that hash directly to the hash that contains it?
result = {
1 => { 1 => "x", 2 => "xx"},
2 => { 1 => "p", 2 => "pp"}
}

Resources