Combining two hashes with a common key - ruby

I have been trying to combine two hashes in Ruby. For example:
h1 = { "a" => 10, "b" => 20, "c"=>34, "d"=>3}
h2 = { "a" => 11, "b" => 21, "d"=>15}
The output I would like is:
h{"a"=> 10, 11, "b"=>20,21, "c"=> 34, "d"=>3,15}
Each hash has the same key, except the second hash might be missing some. I would like the two values then to be represented by the same key.
This is my unsuccessful code:
h1 = { "a" => 10, "b" => 20, "c"=>34, "d"=>3}
h2 = { "a" => 11, "b" => 21, "d"=>15}
h3= h1.update(h2){|key1, key2, val1, val2 |key1,h2_val=h2}
It gives:
{"a"=>{"a"=>11, "b"=>21, "d"=>15}, "b"=>{"a"=>11, "b"=>21, "d"=>15}, "c"=>34, "d"=>{"a"=>11, "b"=>21, "d"=>15}}
I am just new to Ruby so I assume I am missing something very basic here. I would appreciate any help.

What about:
h1 = { "a" => 10, "b" => 20, "c"=>34, "d"=>3}
h2 = { "a" => 11, "b" => 21, "d"=>15}
p h1.merge(h2){|key, old, new| Array(old).push(new) } #=> {"a"=>[10, 11], "b"=>[20, 21], "c"=>34, "d"=>[3, 15]}
And this is how I would write it to combine more than 2 Hashes:
h1 = { "a" => 10, "b" => 20, "c"=>34, "d"=>3}
h2 = { "a" => 11, "b" => 21, "d"=>15}
h3 = { "a" => 11, "b" => 21, "c"=> 1, "d"=>15}
merge_to_array = -> x,y { x.merge(y){|key, old, new| Array(old).push(new)} }
p [h1,h2,h3].reduce &merge_to_array #=> {"a"=>[10, 11, 11], "b"=>[20, 21, 21], "c"=>[34, 1], "d"=>[3, 15, 15]}

h1 = { "a" => 10, "b" => 20, "c"=>34, "d"=>3}
h2 = { "a" => 11, "b" => 21, "d"=>15}
arr= []
arr << h1 << h2
data= arr.map(&:to_a).flatten(1).reduce({}) {|h,(k,v)| (h[k] ||= []) << v; h}
and it gives
{"a"=>[10, 11], "b"=>[20, 21], "c"=>[34], "d"=>[3, 15]}

As example use #reduce:
h1 = { "a" => 10, "b" => 20, "c"=>34, "d"=>3}
h2 = { "a" => 11, "b" => 21, "d"=>15}
h2.reduce(h1.dup) {|h,(k,v)| h[k] = (h[k] && [h[k], v] || v); h}
# => {"a"=>[10, 11], "b"=>[20, 21], "c"=>34, "d"=>[3, 15]}

It's not entirely clear what you're looking for, because your example output is invalid, but here's what I'd do to merge the two hashes without stomping on the keys:
h1 = { "a" => 10, "b" => 20, "c"=>34, "d"=>3}
h2 = { "a" => 11, "b" => 21, "d"=>15}
new_hash = Hash.new{ |h, k| h[k] = [] }
[*h1, *h2].each { |k, v| new_hash[k] << v }
Which results in:
new_hash # => {"a"=>[10, 11], "b"=>[20, 21], "c"=>[34], "d"=>[3, 15]}

Related

How to sum values inside array of hash with same keys

I want to sum the values of same keys like
arr = [{"69120090" => [1, 2, 3]}, {"69120090" => [4, 5, 6]}]
I need to result in:
result = [{"69120090" => [5, 7, 9]}]
Reduce by Hash#merge! with a block:
arr = [{"69120090"=> [1, 2, 3] }, {"69120090"=> [4, 5, 6] }]
arr.each_with_object({}) do |h, acc|
acc.merge!(h) { |_, v1, v2| v1.zip(v2).map(&:sum) }
end
#⇒ {"69120090"=>[5, 7, 9]}
The above accepts any number of hashes with any number of keys each.
Just to have another option, given the array:
arr = [{ a: [1, 2, 3], b:[8,9,0] }, { a: [4, 5, 6], c: [1,2,3] }, { b: [0,1,2], c: [1,2,3] } ]
You could write:
tmp = Hash.new{ |k,v| k[v] = [] }
arr.each { |h| h.each { |k,v| tmp[k] << v } }
tmp.transform_values { |k| k.transpose.map(&:sum) }
Which returns
tmp #=> {:a=>[5, 7, 9], :b=>[8, 10, 2], :c=>[2, 4, 6]}
As one liner:
(arr.each_with_object(Hash.new{ |k,v| k[v] = [] }) { |h, tmp| h.each { |k,v| tmp[k] << v } }).transform_values { |k| k.transpose.map(&:sum) }

Flatten Hash while Iterating over hash's array elements. Ruby

The input hash can have nests of any combo of Arrays and Hashes (AoA, AoH, HoH, and HoA). Flatting the hash elements to have the proper key and delimiter of _> is no problem.
However, I'm having trouble when an Array comes into the picture and I need to grab each element and stick it to the proper key while continuing to build the output. The final output should be a 1-D array of hashes with the only difference being the each array elements.
For example:
if the input hash is:
{:x => 333, :y => 13, :z => [1,2,{:zz => [40,50]},[10,20]], :a => {:o => "1", :p => "2"}}
The final result should be:
`[{:x => 333, :y => 13, :z => 1, :z_>zz => 40, :a_>o => 1, a_>p => 2},
{:x => 333, :y => 13, :z => 1, :z_>zz => 50, :a_>o => 1, a_>p => 2},
{:x => 333, :y => 13, :z => 2, :z_>zz => 40, :a_>o => 1, a_>p => 2},
{:x => 333, :y => 13, :z => 2, :z_>zz => 50, :a_>o => 1, a_>p => 2},
{:x => 333, :y => 13, :z => 10, :z_>zz => 40, :a_>o => 1, a_>p => 2},
{:x => 333, :y => 13, :z => 10, :z_>zz => 50, :a_>o => 1, a_>p => 2},
{:x => 333, :y => 13, :z => 20, :z_>zz => 40, :a_>o => 1, a_>p => 2},
{:x => 333, :y => 13, :z => 20, :z_>zz => 50, :a_>o => 1, a_>p => 2}]`
This is long and complicated, but at least it works:
my_hash = {:x => 333, :y => 13, :z => [1,2,{:zz => [40,50]},[10,20]], :a => {:o => "1", :p => "2"}}
# Create Recursive function to get values:
def advance_hash_flattener(input, parent=[])
case input
when Hash then input.flat_map{|key, val|
advance_hash_flattener(val, parent+[key])}
when Array then input.flat_map{|x| advance_hash_flattener(x, parent)}
else [parent.join('_>'), input]
end
end
#Some small transformations for the last step:
first_step = advance_hash_flattener(my_hash)
.each_slice(2)
.group_by{|x| x.first}
.map{|x| [x.first, x.last.map(&:last)]}
p first_step #=> [["x", [333]], ["y", [13]], ["z", [1, 2, 10, 20]], ["z_>zz", [40, 50]], ["a_>o", ["1"]], ["a_>p", ["2"]]]
# Create an array of Hashes:
final_array = [Hash.new]
first_step.each do |key,values|
new = []
values.each do |val|
if final_array.first.key?(key)
final_copy = final_array.map{|x|x.clone}
final_copy.each{|x| x[key] = val}
new += final_copy
else
final_array.each{|x| x[key] = val}
end
end
final_array += new
end
# result stored in final_array

Subtract values in hash from corresponding values in another hash

I'd like to be able to subtract two hashes and get a third hash in Ruby.
The two hashes look like this:
h1 = {"Cat" => 100, "Dog" => 5, "Bird" => 2, "Snake" => 10}
h1.default = 0
h2 = {"cat" => 50, "dog" => 3, "BIRD" => 4, "Mouse" => 75, "Snake" => 10}
h2.default = 0
I'd like to be able to call a method on h1 like this:
h1.difference(h2)
and get this hash as a result:
{"Cat" => 50, "Dog" => 2, "BIRD" => -2, "Mouse" => -75}
I'd like to create a new hash with keys from both Hashes and the values of the new hash to be the value of the key in the first hash minus the value of that key in the second hash. The catch is that I'd like this Hash method to work regardless of the case of the keys. In other words, I'd like "Cat" to match up with "cat".
Here's what I have so far:
class Hash
def difference(another_hash)
(keys + another_hash.keys).map { |key| key.strip }.uniq.inject(Hash.new(0)) { |acc, key| acc[key] = (self[key] - another_hash[key]); acc }.delete_if { |key, value| value == 0 }
end
end
This is OK, but, unfortunately, the result isn't what I want.
Any help would be appreciated.
How about converting the hashes to sets.
require 'set'
h1 = {"Cat" => 100, "Dog" => 5, "Bird" => 2, "Snake" => 10}
h1.default = 0
h2 = {"cat" => 50, "dog" => 3, "BIRD" => 4, "Mouse" => 75, "Snake" => 10}
h2.default = 0
p (h1.to_set - h2.to_set)
#=> #<Set: {["Cat", 100], ["Dog", 5], ["Bird", 2]}>
As a recommendation...
I've used something like this in the past:
class Hash
def downcase_keys
Hash[map{ |k,v| [k.downcase, v]}]
end
def difference(other)
Hash[self.to_a - other.to_a]
end
alias :- :difference
end
which lets me do things like:
irb(main):206:0> h1.downcase_keys - h2.downcase_keys
{
"cat" => 100,
"dog" => 5,
"bird" => 2
}
irb(main):207:0> h2.downcase_keys - h1.downcase_keys
{
"cat" => 50,
"dog" => 3,
"bird" => 4,
"mouse" => 75
}
The alias gives you the nice syntax of using - instead of difference, similar to using - for Arrays and Sets. You can still use difference though:
irb(main):210:0> h1.downcase_keys.difference(h2.downcase_keys)
{
"cat" => 100,
"dog" => 5,
"bird" => 2
}
irb(main):211:0> h2.downcase_keys.difference(h1.downcase_keys)
{
"cat" => 50,
"dog" => 3,
"bird" => 4,
"mouse" => 75
}
I always normalize my hash keys, and don't allow variants to leak in. It makes processing the hashes much too difficult when you don't know what the keys are called, so I'd highly recommend doing that as a first step. It's a code-maintenance issue.
Otherwise, you could create a map of the original key names and their normalized names, but you run into problems if your hash contains two unique-case keys, such as 'key' and 'KEY', because normalizing will stomp on one.
Sorry that due to the time limit (I have to take care of my baby boy now), only figured out this stupid but working code:
h1 = {"Cat" => 100, "Dog" => 5, "Bird" => 2, "Snake" => 10}
h1.default = 0
h2 = {"cat" => 50, "dog" => 3, "BIRD" => 4, "Mouse" => 75, "Snake" => 10}
h2.default = 0
h3 = {"Cat" => 50, "Dog" => 2, "BIRD" => -2, "Mouse" => -75}
class Hash
def difference(subtrahend)
diff = {}
self.each_pair do |k1, v1|
flag = false
subtrahend.each_pair do |k2, v2|
if k1.downcase == k2.downcase
flag = true
v_diff = v1 - v2
break if v_diff == 0
v_diff > 0 ? diff[k1] = v_diff : diff[k2] = v_diff
end
end
diff[k1] = v1 unless flag
end
subtrahend.each_pair do |k2, v2|
flag = false
self.each_pair do |k1, v1|
if k1.downcase == k2.downcase
flag = true
break
end
end
diff[k2] = -v2 unless flag
end
return diff
end
end
h1.difference(h2) == h3 ? puts("Pass") : puts("Fail") #=> "Pass"
I got this to the resque https://github.com/junegunn/insensitive_hash
then follow your procedure but slighly tweaked as requirement
require 'insensitive_hash'
h1 = {"Cat" => 100, "Dog" => 5, "Bird" => 2, "Snake" => 10}.insensitive
h1.default = 0
h2 = {"cat" => 50, "dog" => 3, "BIRD" => 4, "Mouse" => 75, "Snake" => 10}.insensitive
h2.default = 0
class Hash
def difference(another_hash)
(keys + another_hash.keys).map { |key|
key.downcase }.uniq.inject(Hash.new(0)) do |acc, key|
val = self[key] - another_hash[key]
acc[key] = val if val!= 0
acc
end
end
end
h1.difference(h2)
# => {"cat"=>50, "dog"=>2, "bird"=>-2, "mouse"=>-75}
This time I would like to provide another solution: normalized -> store original key value pairs -> grab the original key who has larger value as the key for the difference.
h1 = {"Cat" => 100, "Dog" => 5, "Bird" => 2, "Snake" => 10}
h1.default = 0
h2 = {"cat" => 50, "dog" => 3, "BIRD" => 4, "Mouse" => 75, "Snake" => 10}
h2.default = 0
h3 = {"Cat" => 50, "Dog" => 2, "BIRD" => -2, "Mouse" => -75}
class Hash
def difference(subtrahend)
# create a hash which contains all normalized keys
all_pairs = (self.keys.map{|x| x.downcase} + subtrahend.keys.map{|x| x.downcase}).uniq.inject({}) do |pairs, key|
pairs[key] = []
pairs
end
#=> {"mouse"=>[], "cat"=>[], "snake"=>[], "bird"=>[], "dog"=>[]}
# push original key value pairs into array which is the value of just created hash
[self, subtrahend].each_with_index do |hsh, idx|
hsh.each_pair { |k, v| all_pairs[k.downcase].push([k, v]) }
all_pairs.each_value { |v| v.push([nil, 0]) if v.size == idx }
end
#=> {"mouse"=>[[nil, 0], ["Mouse", 75]], "cat"=>[["Cat", 100], ["cat", 50]], "snake"=>[["Snake", 10], ["Snake", 10]], "bird"=>[["Bird", 2], ["BIRD", 4]], "dog"=>[["Dog", 5], ["dog", 3]]}
results = {}
all_pairs.each_value do |values|
diff = values[0][1] - values[1][1]
# always take the key whose value is larger
if diff > 0
results[values[0][0]] = diff
elsif diff < 0
results[values[1][0]] = diff
end
end
return results
end
end
puts h1.difference(h2).inspect #=> {"Cat" => 50, "Dog" => 2, "BIRD" => -2, "Mouse" => -75}
h1.difference(h2) == h3 ? puts("Pass") : puts("Fail") #=> "Pass"
According to what you described, this one does a pretty good job. The result is exactly what you've shown (key is not normalized in the final result, but depends on whose value is bigger).

ruby - group by repeating key of multiple hashes

I have the following array
t = [
{nil => 1, 10 => 2, 16 => 4, 5=> 10},
{nil => 9, 5 => 2, 17 => 3, 10 => 2},
{10 => 4, 5 => 9, 17 => 1}
]
how can I get this as result?
{nil => [1,9,0],10 => [2,2,4], 16 => [4,0,0], 5 => [10,2,9], 17=>[0,3,1]}
I've seen that I can use something like this
t.group_by{|h| h['key']}
but I'm not sure if I can put a regexp inside the brackets
Thanks in advance
Javier
EDIT:
Is just want to group by each key of each hash inside the array, if the key is not present then the value is 0 for that hash
How about this one for illegibility:
t = [
{nil => 1, 10 => 2, 16 => 4, 5=> 10},
{nil => 9, 5 => 2, 17 => 3, 10 => 2},
{10 => 4, 5 => 9, 17 => 1}
]
# Create hash of possible keys
keys = t.reduce({}) { |m, h| h.each_key { |k| m[k] = [] }; m }
# Iterate through array, for each hash, for each key, append the
# value if key is in hash or zero otherwise
t.reduce(keys) { |m, h| m.each_key { |k| m[k] << (h[k] || 0) }; m }
puts keys
#=> {nil=>[1, 9, 0], 10=>[2, 2, 4], 16=>[4, 0, 0], 5=>[10, 2, 9], 17=>[0, 3, 1]}
Not the most elegant code I've ever written, but it does the job and is easy to understand:
def jqq(a)
keys = []
result = {}
a.each do |h|
keys += h.keys
end
keys.uniq.each do |key|
result[key] = []
a.each do |h|
h.default = 0
result[key] << h[key]
end
end
result
end
t = [
{nil => 1, 10 => 2, 16 => 4, 5=> 10},
{nil => 9, 5 => 2, 17 => 3, 10 => 2},
{10 => 4, 5 => 9, 17 => 1}
]
puts jqq(t)
# {nil=>[1, 9, 0], 10=>[2, 2, 4], 16=>[4, 0, 0], 5=>[10, 2, 9], 17=>[0, 3, 1]}
I do not think there is any any function available
Just gave a try with hash
def do_my_work(data)
hash = {}
#get all keys first
arr.map{|m| m.keys}.flatten.uniq.each {|a| hash[a]=[]}
# Now iterate and fill the values
arr.each do |elm|
hash.each do |k,v|
hash[k] << (elm[k].nil? ? 0 : elm[k])
end
end
end
hash = do_my_work(t)
puts hash
# => {nil=>[1, 9, 0], 10=>[2, 2, 4], 16=>[4, 0, 0], 5=>[10, 2, 9], 17=>[0, 3, 1]}

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.

Resources