hash deep_merge with arrays - ruby

I've searched through all the answers regarding the use of deep_merge; however, I am still having trouble with my particular issue. I'm trying to merge 2 hashes and add a specific key for each match. For example:
UPDATED THE FORMAT
Hash 1:
{
"actions"=> [
{
"causes"=> [
{
"shortDescription"=>"short description for run 1",
"userId"=>"user.a"
}
]
}
],
"artifacts"=> [],
"fullDisplayName"=>"Run #1",
"result"=>"FAILURE",
"changeSet"=> {
"items"=>[],
"kind"=>nil
},
"culprits"=> []
}
Hash 2:
{
"actions"=> [
{
"causes"=> [
{
"shortDescription"=>"short description for run 2",
"userId"=>"user.b"
}
]
}
],
"artifacts"=> [],
"fullDisplayName"=>"Run #2",
"result"=>"FAILURE",
"changeSet"=> {
"items"=>[],
"kind"=>nil
},
"culprits"=> []
}
Key list:
["key-one","key-two"]
I would like the resulting hash to be:
{
"actions"=> [
{
"causes"=> [
{
"shortDescription"=> {
"key-one" => "short description for run 1",
"key-two" => "short description for run 2"
},
"userId"=> {
"key-one" => "user.a",
"key-two" => "user.b"
}
}
]
}
],
"artifacts"=> {
"key-one" => [],
"key-two" => []
},
"fullDisplayName"=> {
"key-one" => "Run #1",
"key-two" => "Run #2"
},
"result"=> {
"key-one" => "FAILURE",
"key-two" => "FAILURE"
},
"changeSet"=> {
"items"=> {
"key-one" => [], "key-two" => []
},
"kind"=> {
"key-one" => nil,
"key-two" => nil
}
},
"culprits"=> {
"key-one" => [],
"key-two" => []
}
}

If h1 and h2 are the two hashes you wish to merge, and h3 is the desired result, I think the following should work:
#merge_key1, #merge_key2 = "key-one", "key-two"
def merge_em(g1, g2)
case g1
when Array
g1.size.times {|i| merge_em(g1[i], g2[i])}
when Hash
g1.keys.each do |k|
v = g1[k]
if (Hash === v || (Array === v && !v.empty?))
merge_em(v, g2[k])
else
g1[k] = {#merge_key1 => v, #merge_key2 => g2[k]}
end
end
end
end
h = Marshal.load(Marshal.dump(h1))
merge_em(h, h2)
p (h == h3) # => true
A few notes:
This solution depends on the hash structures; it may not work if the structures are changed.
Marshal.load(Marshal.dump(h1)) is used to make a "deep copy" of h1, so that h1 is not modified. If h1 can be modified, merge_em(h1, h2) is sufficient, with h1 being the merged hash.
The gem awesome_print gives you a nicely-formatted display of complex structures like these. All you'd need to do here is require 'ap' followed by ap h. Try it!

Related

Group array of hashes by value and retain structure (hash) in Ruby

I have a hash like this:
hash = {
'en-us': {
where: 'USA'
},
'en-us-zone2': {
where: 'USA'
},
'en-nl': {
where: 'Netherlands'
},
'en-pt': {
where: 'Portugal'
},
}
And I tried to group them using group_by:
result = hash.group_by { |k,v| v[:where] }
However, It returns full of array not by array of hashes. So here is expected and actual results:
Actual Result:
{ "USA"=>
[[:"en-us", {:where=>"USA"}], [:"en-us-zone2", {:where=>"USA"}]],
"Netherlands"=>
[[:"en-nl", {:where=>"Netherlands"}]],
"Portugal"=>
[[:"en-pt", {:where=>"Portugal"}]]
}
Expected Result:
{ "USA"=>
[{:"en-us" => {:where=>"USA"}}, {:"en-us-zone2" => {:where=>"USA"}}]
"Netherlands"=>
[{:"en-nl" => {:where=>"Netherlands"}}]
"Portugal"=>
[{:"en-pt" => {:where=>"Portugal"}}]
}
See, Actual is Array of arrays, instead of array of hashes. Hash keys becomes first array element.
How can I group my hash based on :where ?
This should work:
hash.group_by { |k,v| v[:where] }.each { |_, v| v.map! { |array| { array[0] => array[1] } } }
Or with transform_values
hash.group_by { |k,v| v[:where] }.transform_values { |v| v.map { |array| {array[0] => array[1] } } }
https://ruby-doc.org/core-2.4.0/Hash.html#method-i-transform_values
It's ugly but works:
hash = {
'en-us': {
where: 'USA'
},
'en-us-zone2': {
where: 'USA'
},
'en-nl': {
where: 'Netherlands'
},
'en-pt': {
where: 'Portugal'
},
}
hash.
group_by { |_, v| v[:where] }.
map do |k, v|
[
k,
v.map { |a, b| {a => b} }
]
end.to_h
# => {"USA"=>[{:"en-us"=>{:where=>"USA"}}, {:"en-us-zone2"=>{:where=>"USA"}}], "Netherlands"=>[{:"en-nl"=>{:where=>"Netherlands"}}], "Portugal"=>[{:"en-pt"=>{:where=>"Portugal"}}]}

Is there a more efficient way to refactor the iteration of the hash on ruby?

I have a iteration here:
container = []
summary_data.each do |_index, data|
container << data
end
The structure of the summary_data is listed below:
summary_data = {
"1" => { orders: { fees: '25.00' } },
"3" => { orders: { fees: '30.00' } },
"6" => { orders: { fees: '45.00' } }
}
I want to remove the numeric key, e.g., "1", "3".
And I expect to get the following container:
[
{
"orders": {
"fees": "25.00"
}
},
{
"orders": {
"fees": "30.00"
}
},
{
"orders": {
"fees": "45.00"
}
}
]
Is there a more efficient way to refactor the code above?
Appreciate for any help.
You can use Hash#values method, like this:
container = summary_data.values
If the inner hashes all have the same structure, the only interesting information are the fees:
summary_data.values.map{|h| h[:orders][:fees] }
# => ["25.00", "30.00", "45.00"]
If you want to do some calculations with those fees, you could convert them to numbers:
summary_data.values.map{|h| h[:orders][:fees].to_f }
# => [25.0, 30.0, 45.0]
It might be even better to work with cents as integers to avoid any floating point error:
summary_data.values.map{|h| (h[:orders][:fees].to_f * 100).round }
=> [2500, 3000, 4500]
You need an array having values of provided hash. You can get by values method directly.
summary_data.values

How do I assign part of a hash to another variable?

Trying to assign part of a has to another variable. I have a hash. Something like:
hash = {
"cupcake" => {
"a" => 1
},
"muffin" => {
"b" => 2
}
}
When I do something like:
cupcake = hash["cupcake"]
cupcake is nil after this code.
If you want string keys you have to use this syntax
hash = {
"cupcake" => {
"a" => 1
},
"muffin" => {
"b" => 2
}
}
Syntax with colons is for symbol keys
hash = {
cupcake: {
a: 1
},
muffin: {
b: 2
}
}
cupcake = hash[:cupcake]

Populating a hash from an array

I have this array:
params[:types] = [type1, type2, type3...]
I would like to populate my hash the following way using the above array:
params[:hash] = {
"type1" => {
something: something
},
"type2" => {
something: something
},
}
Using a for loop like for index in i ...params[:types] just populates the hash with the last value in the array.
You can use the each_with_object method to do this:
params = {}
params[:types] = ["type1", "type2", "type3"]
params[:types].each_with_object({}) { |k, h| h[k] = { "something" => "something" } }
That last line will return:
=> {"type1"=>{"something"=>"something"}, "type2"=>{"something"=>"something"}, "type3"=>{"something"=>"something"}}
Here is a code snippet example that does what you need.
hash = {}
array.each do |a|
hash[a.to_s] = { "something" => "something" }
end
output:
hash
=> {
"type1" => {
"something" => "something"
},
"type2" => {
"something" => "something"
},
"type3" => {
"something" => "something"
}
}
You could do this:
params = { types: ["type1", "type2", "type3"] }
Hash[params[:types].product([{"something" => "something"}])]
#=> {"type1"=>{"something"=>"something"},
# "type2"=>{"something"=>"something"},
# "type3"=>{"something"=>"something"}}
or with Ruby 2.1,
params[:types].product([{"something" => "something"}]).to_h
If you want a different hash for each element of params[:types]:
hashes = [{ "something1"=>"something1" }, { "something2"=>"something2" },
{ "something3"=>"something3" }]
then
Hash[params[:types].zip(hashes)]
#=> {"type1"=>{"something1"=>"something1"},
# "type2"=>{"something2"=>"something2"},
# "type3"=>{"something3"=>"something3"}}

Split an array of hashes in two based on the value of a key

How can split this array of hashes in two based on the value of the ate key?
array = [
{ name: "Gad", ate: true },
{ name: "Lad", ate: false },
{ name: "Bad", ate: true },
{ name: "Sad", ate: false }
]
Example output
array_1 = [
{ name: "Gad", ate: true },
{ name: "Bad", ate: true }
]
array_2 = [
{ name: "Lad", ate: false },
{ name: "Sad", ate: false }
]
Use the Enumerable#partition method:
array.partition { |x| x[:ate] }
# => [[{:name=>"Gad", :ate=>true}, {:name=>"Bad", :ate=>true}],
# [{:name=>"Lad", :ate=>false}, {:name=>"Sad", :ate=>false}]]
Or:
array_1, array_2 = array.partition { |x| x[:ate] }
array_1
# => [{:name=>"Gad", :ate=>true}, {:name=>"Bad", :ate=>true}]
array_2
# => [{:name=>"Lad", :ate=>false}, {:name=>"Sad", :ate=>false}]
array_one, array_two = *array.group_by { |x| x[:ate] }.map(&:last)
=> array_one
=> # [{:name=>"Gad", :ate=>true}, {:name=>"Bad", :ate=>true}]
=> array_two
=> # [{:name=>"Lad", :ate=>false}, {:name=>"Sad", :ate=>false}]
thx #CarySwoveland
I can't compete with partition, but here's another way:
trues = array.select { |h| h[:ate] }
falses = array - trues

Resources