ruby turn a nested array into a hash - arrays

Is there an elegant way to turn a nested array of the form
[["a", 1], ["a", 2], [nil, 3], [nil, 4], ["b", 6], ["b", 8]]
into a hash of the form
{"a" => [1,2], nil => [3,4], "b" => [6,8]}

This is one way:
arr = [["a", 1], ["a", 2], [nil, 3], [nil, 4], ["b", 6], ["b", 8]]
h = Hash.new {|hash, key| hash[key] = []}
arr.each {|e| h[e[0]] << e[1]}
p h #=> {"a"=>[1, 2], nil=>[3, 4], "b"=>[6, 8]}

ary = [['a', 1], ['a', 2], [nil, 3], [nil, 4], ['b', 6], ['b', 8]]
ary.group_by(&:first).
# => { 'a' => [['a', 1], ['a', 2]], nil => [[nil, 3], [nil, 4]], 'b' => [['b', 6], ['b', 8]] }
map {|k, v| [k, v.map(&:last)] }.
# => [['a', [1, 2]], [nil, [3, 4]], ['b', [6, 8]]]
to_h
# => { 'a' => [1, 2], nil => [3, 4], 'b' => [6, 8] }

One way could be:
array = [['a', 1], ['a', 2], [nil, 3], [nil, 4], ['b', 6], ['b', 8]]
array.each_with_object(Hash.new{|h,k| h[k] = []}) {|a, obj| obj[a.first] << a.last }
# => {"a"=>[1, 2], nil=>[3, 4], "b"=>[6, 8]}

array.each_with_object({}){|a, h| (h[a.first]||=[] )<< a.last }

ary = [['a', 1], ['a', 2], [nil, 3], [nil, 4], ['b', 6], ['b', 8]]
ary.group_by(&:first).map {|k, v| {k => v.map(&:last)} }

Related

Show only the combinations of two permutated arrays that have a sum less than or equal to target number

I have two arrays:
teams = [1,2,3] and drivers = [4,5,6]. Using permutations I have managed to show all combinations of the two arrays, but have managed to define what number of values I'd like to use from each array. So from 'Teams' I have used 1 value and 'Drivers' I have used two. I would like to only show the combinations where the sum is less than or equal to 10 and remove any duplicates.
teams = [1,2,3]
drivers = [4,5,6]
team = teams.permutation(1).to_a
driver = drivers.permutation(2).to_a
array = team.product(driver)
target = 11
This is successfully outputting all combinations of the two arrays using 1 number from teams and 2 from drivers as follows:
[[1], [4, 5]], [[1], [4, 6]], [[1], [5, 4]], [[1], [5, 6]], [[1], [6, 4]], [[1], [6, 5]], [[2], [4, 5]], etc...
To only show values less than or equal to 10 my expected outcome would be: [[1], [4, 5]], [[1], [5, 4]],
and then no duplicates would leave me with just:
[[1], [4, 5]]
I have tried adding the below line of code but am getting an undefined method `<=' error:
#array = array[0].product(*array[1..-1]).select { |a| a.reduce(:+) <= target }
I have also tried this with no luck:
result = array.combination(1).select{|combi| combi.sum <= target}
#array = result
I'm guessing it's something to do with the permutation?
teams = [1,2,3]
drivers = [2,5,4,5,6,4,5,7]
max_driver_sum = 10
I have assumed that drivers can contain duplicate elements (as in my example), but I will explain at the end how the calculations would simplify if there are no duplicates.
As a first step let's partition drivers between values that are repeated and those that are not.
counts = drivers.tally
#=> {2=>1, 5=>3, 4=>2, 6=>1, 7=>1}
dup_drivers, uniq_drivers = counts.partition { |_d,n| n > 1 }
.map { |arr| arr.map(&:first) }​
#=> [[5, 4], [2, 6, 7]]
​Therefore,
dup_drivers
#=> [5, 4]
uniq_drivers
#=> [2, 6, 7]
See Enumerable#tally and Enumerable#partition.
Here,
counts.partition { |_d,n| n > 1 }
#=> [[[5, 3], [4, 2]], [[2, 1], [6, 1], [7, 1]]]
First compute the unique combinations in which the two drivers are equal:
dup_combos = teams.each_with_object([]) do |t,arr|
max_driver = (max_driver_sum - t)/2
dup_drivers.each do |d|
arr << [[t],[d,d]] if d <= max_driver
end
end
#=> [[[1], [4, 4]], [[2], [4, 4]]]
Next, compute the unique combinations in which the two drivers are not equal:
all_uniq = uniq_drivers + dup_drivers
#=> [2, 6, 7, 5, 4]
all_uniq_combos = all_uniq.combination(2).to_a
#=> [[2, 6], [2, 7], [2, 5], [2, 4], [6, 7], [6, 5],
# [6, 4], [7, 5], [7, 4], [5, 4]]
uniq_combos = teams.each_with_object([]) do |t,arr|
adj_driver_sum = max_driver_sum - t
all_uniq_combos.each do |combo|
arr << [[t],combo] if combo.sum <= adj_driver_sum
end
end
#=> [[[1], [2, 6]], [[1], [2, 7]], [[1], [2, 5]], [[1], [2, 4]],
# [[1], [5, 4]], [[2], [2, 6]], [[2], [2, 5]], [[2], [2, 4]],
# [[3], [2, 5]], [[3], [2, 4]]]
See Array#combination.
The final step is to combine the two groups of combinations:
a1 = dup_combos + uniq_combos
#=> [[[1], [4, 4]], [[2], [4, 4]], [[1], [2, 6]], [[1], [2, 7]],
# [[1], [2, 5]], [[1], [2, 4]], [[1], [5, 4]], [[2], [2, 6]],
# [[2], [2, 5]], [[2], [2, 4]], [[3], [2, 5]], [[3], [2, 4]]]
Sorted, this result is as follows.
a1.sort
#=> [[[1], [2, 4]], [[1], [2, 5]], [[1], [2, 6]], [[1], [2, 7]],
# [[1], [4, 4]], [[1], [5, 4]],
# [[2], [2, 4]], [[2], [2, 5]], [[2], [2, 6]], [[2], [4, 4]],
# [[3], [2, 4]], [[3], [2, 5]]]
Notice that Array#uniq was not used in the foregoing. If desired, one could of course substitute out some of the variables above.
If drivers contains no duplicates the desired array is given by uniq_combos where all_uniq is replaced by drivers in the calculation of all_uniq_combos. If, for example,
teams = [1,2,3]
drivers = [2,5,4,6,7]
max_driver_sum = 10
then
all_uniq_combos = drivers.combination(2).to_a
#=> [[2, 5], [2, 4], [2, 6], [2, 7], [5, 4], [5, 6],
# [5, 7], [4, 6], [4, 7], [6, 7]]
combos = teams.each_with_object([]) do |t,arr|
adj_driver_sum = max_driver_sum - t
all_uniq_combos.each do |combo|
arr << [[t],combo] if combo.sum <= adj_driver_sum
end
end ​
#=> [[[1], [2, 5]], [[1], [2, 4]], [[1], [2, 6]], [[1], [2, 7]],
# [[1], [5, 4]], [[2], [2, 5]], [[2], [2, 4]], [[2], [2, 6]],
# [[3], [2, 5]], [[3], [2, 4]]]
combos.sort
#=> [[[1], [2, 4]], [[1], [2, 5]], [[1], [2, 6]], [[1], [2, 7]],
# [[1], [5, 4]],
# [[2], [2, 4]], [[2], [2, 5]], [[2], [2, 6]],
# [[3], [2, 4]], [[3], [2, 5]]]
Here's an approach
teams = [1, 2, 3]
drivers = [2, 6, 5, 4]
team = teams.permutation(1).to_a
driver = drivers.permutation(2).to_a
array = team.product(driver)
target = 10
res = array.select {|i| i.map(&:sum).sum <= target}.compact
==> [[[1], [2, 6]], [[1], [2, 5]], [[1], [2, 4]], [[1], [6, 2]],
[[1], [5, 2]], [[1], [5, 4]], [[1], [4, 2]], [[1], [4, 5]],
[[2], [2, 6]], [[2], [2, 5]], [[2], [2, 4]], [[2], [6, 2]],
[[2], [5, 2]], [[2], [4, 2]], [[3], [2, 5]], [[3], [2, 4]],
[[3], [5, 2]], [[3], [4, 2]]]
Getting the unique items (modified to also work for values of teams > drivers)
t1 = res.map {|i| i[0]}
d2 = res.map {|i| i[1].flatten.sort}
t1.zip(d2).uniq
==> [[[1], [2, 6]], [[1], [2, 5]], [[1], [2, 4]], [[1], [4, 5]],
[[2], [2, 6]], [[2], [2, 5]], [[2], [2, 4]], [[3], [2, 5]],
[[3], [2, 4]]]

Array combinatorics

I am working in Ruby 2.7.
I have an Array, whose elements are one of three things: either a String or an Integer, or occasionally another Array with a pair of elements (this time, only Strings or Integers). There are no further levels of nesting for Arrays, by the definition of my problem.
Examples:
w = [1, 2, 'b', 4]
x = [[2, 'r'],[2, 'g']]
y = [[2, 'w']]
z = ['u', 6, [2, 'r']]
The context of the problem is that the inner arrays represent selectable alternatives for use in the outer array. I am looking for all possible ways to choose an element from each inner array, if present, and substitute that element into the outer array.
Examples:
f(w) = [ [1, 2, 'b', 4] ]
f(x) = [ [2, 2], [2, 'g'], ['r', 2], ['r', 'g'] ]
f(y) = [ [2], ['w'] ]
f(z) = [ ['u', 6, 2], ['u', 6, 'r'] ]
Order of elements in each selection (inner array of each solution) does not matter. Order of solutions in output also does not matter.
I can brute force an answer selecting/rejecting, transforming, and using Array#product; but I am looking for an approach that is as brief and elegant as possible.
The rules given in the question are unprecise and incomplete, so I've done my best to infer what they are.
Code
def doit(arr)
if arr.size == 1
e = arr.first
return e.is_a?(Array) ? (arr.map { |f| [f] }) : arr
end
(first, *rest), other = arr.partition { |e| e.is_a?(Array) }
return [arr] if first.nil?
prod = first.product(*rest)
return prod if other.empty?
prod.flat_map { |a| other.map { |e| [e, *a] } }
end
See Enumerable#partition, Array#product and Enumerable#flat_map.
Examples
doit [1, 2, 'b', 4]
#=> [[1, 2, "b", 4]]
doit [[2, 'r'],[2, 'g']]
#=> [[2, 2], [2, "g"], ["r", 2], ["r", "g"]]
doit [:a, [2, 'r'], :b, [2, 'g']]
#=> [[:a, 2, 2], [:b, 2, 2], [:a, 2, "g"], [:b, 2, "g"],
# [:a, "r", 2], [:b, "r", 2], [:a, "r", "g"], [:b, "r", "g"]]
doit [[2, 'w']]
#=> [[[2, "w"]]]
doit ['u', 6, [2, 'r']]
#=> [["u", 2], [6, 2], ["u", "r"], [6, "r"]]
doit [:a, [2, 3], :b, :c, [4, 5], :d, [6, 7]]
#=> [[:a, 2, 4, 6], [:b, 2, 4, 6], [:c, 2, 4, 6], [:d, 2, 4, 6],
# [:a, 2, 4, 7], [:b, 2, 4, 7], [:c, 2, 4, 7], [:d, 2, 4, 7],
# [:a, 2, 5, 6], [:b, 2, 5, 6], [:c, 2, 5, 6], [:d, 2, 5, 6],
# [:a, 2, 5, 7], [:b, 2, 5, 7], [:c, 2, 5, 7], [:d, 2, 5, 7],
# [:a, 3, 4, 6], [:b, 3, 4, 6], [:c, 3, 4, 6], [:d, 3, 4, 6],
# [:a, 3, 4, 7], [:b, 3, 4, 7], [:c, 3, 4, 7], [:d, 3, 4, 7],
# [:a, 3, 5, 6], [:b, 3, 5, 6], [:c, 3, 5, 6], [:d, 3, 5, 6],
# [:a, 3, 5, 7], [:b, 3, 5, 7], [:c, 3, 5, 7], [:d, 3, 5, 7]]
Note that the return value for doit [[2, 'w']] is different than that given in the question.
Explanation
The steps are as follows.
arr = [:a, [2, 'r'], :b, [2, 'g']]
(first, *rest), other = arr.partition { |e| e.is_a?(Array) }
#=> [[[2, "r"], [2, "g"]], [:a, :b]]
Ruby applies array decomposition to the above expression to obtain values for first, rest and other:
first
#=> [2, "r"]
rest
#=> [[2, "g"]]
other
#=> [:a, :b]
Continuing, because first.nil? #=> false in return [arr] if first.nil?, we do not return. Next:
prod = first.product(*rest)
#=> [[2, 2], [2, "g"], ["r", 2], ["r", "g"]]
other.empty? #=> false in return prod if other.empty? so we do not return.
I can best explain the remaining calculations by inserting puts statement in the code and running it.
prod.flat_map do |a|
puts "a = #{a}"
other.map do |e|
puts " e = :#{e}"
puts " [e, *a] = #{[e, *a]}"
[e, *a]
end
end
#=> [[:a, 2, 2], [:b, 2, 2], [:a, 2, "g"], [:b, 2, "g"],
# [:a, "r", 2], [:b, "r", 2], [:a, "r", "g"], [:b, "r", "g"]]
The following is displayed.
a = [2, 2]
e = :a
[e, *a] = [:a, 2, 2]
e = :b
[e, *a] = [:b, 2, 2]
a = [2, "g"]
e = :a
[e, *a] = [:a, 2, "g"]
e = :b
[e, *a] = [:b, 2, "g"]
a = ["r", 2]
e = :a
[e, *a] = [:a, "r", 2]
e = :b
[e, *a] = [:b, "r", 2]
a = ["r", "g"]
e = :a
[e, *a] = [:a, "r", "g"]
e = :b
[e, *a] = [:b, "r", "g"]

How to get ALL combinations of array elements in Ruby?

If I have an array:
%w(a b c d e)
=> ["a","b","c","d","e"]
I can get some combinations with
irb(main):071:0> %w(a b c d e).combination(3).to_a
=> [["a", "b", "c"], ["a", "b", "d"], ["a", "b", "e"], ["a", "c", "d"], ["a", "c", "e"], ["a", "d", "e"], ["b", "c", "d"], ["b", "c", "e"], ["b", "d", "e"], ["c", "d", "e"]]
However that is not ALL the combinations, just the unique ones, e.g. ["e", "a", "b"] is missing
When I similarly try with s smaller array I only get one result:
irb(main):059:0> %w(a b c).combination(3).to_a
=> [["a", "b", "c"]]
How can I get all 6 combinations, i.e. for ['a', 'b', 'c'] I want to get
[['a','b','c'], ['a','c','b'], ['b', 'a', 'c'], ['b', 'c', 'a'], ['c', 'a', 'b'], ['c', 'b', 'a']
Similarly for [1,2,3,4] if I want all the 3 digit combo I should get
irb(main):074:0> [[1,2,3],[1,2,4],[1,3,2],[1,3,4],[1,4,2],[1,4,3], [2,1,3],[2,1,4],[2,3,4],[2,3,1],[2,4,1],[2,4,2], [3,1,2],[3,1,4],[3,2,3],[3,2,4],[3,4,2],[3,4,1]]
?
You are looking for permutation instead of combination.
In combinations, we do not care about the order of the elements, and only care about the presence of all the elements in the set.
[1,2,3,4].permutation(3).to_a
#=> [[1, 2, 3], [1, 2, 4], [1, 3, 2], [1, 3, 4], [1, 4, 2], [1, 4, 3], [2, 1, 3], [2, 1, 4], [2, 3, 1], [2, 3, 4], [2, 4, 1], [2, 4, 3], [3, 1, 2], [3, 1, 4], [3, 2, 1], [3, 2, 4], [3, 4, 1], [3, 4, 2], [4, 1, 2], [4, 1, 3], [4, 2, 1], [4, 2, 3], [4, 3, 1], [4, 3, 2]]

Push array into array on ruby by just one level

Given:
a = [[1,"a"],[2,"b"]]
b = [[3,"c"],[4,"d"]]
I want to turn a into [[1,"a"],[2,"b"][3,"c"],[4,"d"]]. How can do this without +? It creates a new array, which I want to avoid.
(a << b).flatten(1)
# => [1, "a", 2, "b", [3, "c"], [4, "d"]]
a.concat(b)
...............................
> b.inject(a, :<<)
#=> [[1, "a"], [2, "b"], [3, "c"], [4, "d"]]
a = [[1,"a"],[2,"b"]]
b = [[3,"c"],[4,"d"]]
a[a.length, 0] = b
a
# > [[1, "a"], [2, "b"], [3, "c"], [4, "d"]]
concat is the answer, but you could do this:
a.object_id #=> 70223889895340
a.replace(a+b) #=> [[1, "a"], [2, "b"], [3, "c"], [4, "d"]]
a #=> [[1, "a"], [2, "b"], [3, "c"], [4, "d"]]
a.object_id #=> 70223889895340
What about?
a.push(b.shift) while b.any?
How about this?
a + b
=> [[1, "a"], [2, "b"], [3, "c"], [4, "d"]]

How to add two multidimensional arrays

I want to do the following:
array1 = [[1, 10], [2, 20], [3, 10], [4, 30]]
array2 = [[1, 10], [2, 10], [3, 5], [4, 10]]
I want to add two arrays in such a way that the second element of each subarray will be added. I want the following output.
result = [[1,20],[2,30],[3,15],[4,40]]
[array1, array2].transpose.map{|(k, v1), (_, v2)| [k, v1 + v2]}
# => [[1, 20], [2, 30], [3, 15], [4, 40]]
Another approach as below :
array1 = [[1,10],[2,20],[3,10],[4,30]]
array2 = [[1,10],[2,10],[3,5],[4,10]]
Hash[array1].merge(Hash[array2]) { |key,old,new| old + new }.to_a
# => [[1, 20], [2, 30], [3, 15], [4, 40]]
Taking the help of merge(other_hash){|key, oldval, newval| block} .
This can be achieved with a combination of Array#zip and Array#map:
result = array1.zip(array2).map { |l, r| [l[0], l[1] + r[1]] }
#=> [[1, 20], [2, 30], [3, 15], [4, 40]]
However, key-value pairs are often best treated as a Hash. Among other operations, this will allow you to #merge:
hash1
#=> {1=>10, 2=>20, 3=>10, 4=>30}
hash2
#=> {1=>10, 2=>10, 3=>5, 4=>10}
result = hash1.merge(hash2) { |_, l, r| l + r }
#=> {1=>20, 2=>30, 3=>15, 4=>40}

Resources