Reputation: 8588
Given the following arrays:
arr1 = [
{ nested_test1: [1,2] },
{ nested_test2: [true, false] }
]
arr2 = [
{ test1: 'a', test2: 'b' }
]
I wanna loop over array arr2
and mutate it depending on the contents of arr1
. The final result should look like this:
res_arr = [
{ test1: 'a', test2: 'b', nested_test1: 1, nested_test2: true },
{ test1: 'a', test2: 'b', nested_test1: 2, nested_test2: true },
{ test1: 'a', test2: 'b', nested_test1: 1, nested_test2: false },
{ test1: 'a', test2: 'b', nested_test1: 2, nested_test2: false },
]
So my initial setup looks like this:
res_arr = []
arr1.each do |hash1|
arr2.each do |hash2|
hash1.values.first.each do |value|
hash2[hash1.keys.first] = value
res_arr << hash2.clone
end
end
end
But this gives me the following:
res_arr = [
{:test1=>"a", :test2=>"b", :nested_test1=>1},
{:test1=>"a", :test2=>"b", :nested_test1=>2},
{:test1=>"a", :test2=>"b", :nested_test1=>2, :nested_test2=>true},
{:test1=>"a", :test2=>"b", :nested_test1=>2, :nested_test2=>false}
]
So what I wanna do then, is to use the mutated version for each iteration of arr2
. How can this be achieved?
Upvotes: 0
Views: 138
Reputation: 110685
Code
The following method permits its arguments to be arrays of arbitrary size.
def expand(arr1, arr2)
arr2.product(
arr1.map { |h| h.flatten.then { |k,v| v.map { |e| { k=>e } } } }.
then { |first, *rest| first.product(*rest) }
).map { |first, (*rest)| first.merge(*rest.flatten) }
end
Example
arr1 = [{ nested_test1: [1,2] }, { nested_test2: [true, false] }]
arr2 = [{ test1: 'a', test2: 'b' }, { test3: 'c', test4: 'd' }]
expand(arr1, arr2)
#=> [{:test1=>"a", :test2=>"b", :nested_test1=>1, :nested_test2=>true},
# {:test1=>"a", :test2=>"b", :nested_test1=>1, :nested_test2=>false},
# {:test1=>"a", :test2=>"b", :nested_test1=>2, :nested_test2=>true},
# {:test1=>"a", :test2=>"b", :nested_test1=>2, :nested_test2=>false},
# {:test3=>"c", :test4=>"d", :nested_test1=>1, :nested_test2=>true},
# {:test3=>"c", :test4=>"d", :nested_test1=>1, :nested_test2=>false},
# {:test3=>"c", :test4=>"d", :nested_test1=>2, :nested_test2=>true},
# {:test3=>"c", :test4=>"d", :nested_test1=>2, :nested_test2=>false}]
Explanation
The following steps are performed for the example.
Noting that:
arr1.map(&:flatten)
#=> [[:nested_test1, [1, 2]], [:nested_test2, [true, false]]]
the first step is the following:
a = arr1.map { |h| h.flatten.then { |k,v| v.map { |e| { k=>e } } }
#=> [[{:nested_test1=>1}, {:nested_test1=>2}],
# [{:nested_test2=>true}, {:nested_test2=>false}]]
See Hash#flatten and Object#then. The latter method made its debut in Ruby v2.6. It is an alias of Object#yield_self
, which was new in v2.5.
Then:
b = a.then { |first, *rest| first.product(*rest) }
#=> [[{:nested_test1=>1}, {:nested_test2=>true}],
# [{:nested_test1=>1}, {:nested_test2=>false}],
# [{:nested_test1=>2}, {:nested_test2=>true}],
# [{:nested_test1=>2}, {:nested_test2=>false}]]
See Array#product. Here and especially below I made heavy use of Array decomposition. See also this article.
Continuing,
c = arr2.product(b)
#=> [[{:test1=>"a", :test2=>"b"}, [{:nested_test1=>1}, {:nested_test2=>true}]],
# [{:test1=>"a", :test2=>"b"}, [{:nested_test1=>1}, {:nested_test2=>false}]],
# [{:test1=>"a", :test2=>"b"}, [{:nested_test1=>2}, {:nested_test2=>true}]],
# [{:test1=>"a", :test2=>"b"}, [{:nested_test1=>2}, {:nested_test2=>false}]],
# [{:test3=>"c", :test4=>"d"}, [{:nested_test1=>1}, {:nested_test2=>true}]],
# [{:test3=>"c", :test4=>"d"}, [{:nested_test1=>1}, {:nested_test2=>false}]],
# [{:test3=>"c", :test4=>"d"}, [{:nested_test1=>2}, {:nested_test2=>true}]],
# [{:test3=>"c", :test4=>"d"}, [{:nested_test1=>2}, {:nested_test2=>false}]]]
and lastly:
c.map { |first, (*rest)| first.merge(*rest.flatten) }
#=> <as shown above>
See Hash#merge.
Upvotes: 0
Reputation: 7356
I'm not entirely clear what your goal here is. For instance, in arr2
, can values of the hash be arrays? In arr1
can the hashes have more than one key value pair? Or what happens if arr2
has multiple hashes in it? Are they merged, or is the result expanded further somehow?
So it's hard to say exactly what the best course is.
Nonetheless, given the data you've given us and the result you say you want, you can get it like this using Array#product:
m = arr1.inject(&:merge)
m.values[0].product(m.values[1]).map {|a| Hash[m.keys.zip(a)].merge(arr2[0]) }
The intuition here is that I think you're trying to create all possible combinations of a couple of arrays, to generate all possible test cases? Depending on what you're trying to do, a more natural way to store the data and get the result might be:
m = {
test1: ['a'],
test2: ['b'],
nested_test1: [1, 2],
nested_test2: [true, false]
}
m.values.inject(&:product).map {|v| Hash[m.keys.zip(v.flatten)] }
Upvotes: 1