Related
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"]
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]]
I currently have an input [['a', [0, 1]], ['b', [1]]]. I'm trying to combine the first item to every element in [0,1] i.e.: 'a' in ['a',[0,1] => [['a',0],['a',1],['b',1]] like ordered pairs. I've done it but it seems overly complicated I thought there might be a method I've overlooked.
[[0, [0, 1]], [1, [1]]].map.with_index{|x,y| x[1].map{|ele| [y,ele]}}.flatten(1)
#I used 'a'&'b' in the example to help with any confusion.
▶ arr.flat_map { |e| [e.first].product(e.last) }
#⇒ [["a", 0], ["a", 1], ["b", 1]]
arr = [['a', [0, 1]], ['b', [1]]]
arr.each_with_object([]) { |(x,a),b| a.each { |y| b << [x,y] } }
#=> [["a", 0], ["a", 1], ["b", 1]]
How would you go about sorting two arrays the same way?
hey = %w[e c f a d b g]
hoo = [1,2,3,4,5,6,7]
hey.sort #=> [a,b,c,d,e,f,g]
hoo.same_sort #=> [4,6,2,5,1,3,7]
Have a try:
hey.zip(hoo).sort
=> [["a", 4], ["b", 6], ["c", 2], ["d", 5], ["e", 1], ["f", 3], ["g", 7]]
hey.zip(hoo).sort.transpose
=> [["a", "b", "c", "d", "e", "f", "g"], [4, 6, 2, 5, 1, 3, 7]]
You can do this with a single sort, using Enumerable#sort_by and Array#values_at:
sorted_indices = hey.each_index.sort_by { |i| hey[i] }
#=> [3, 5, 1, 4, 0, 2, 6]
hey.values_at(*sorted_indices)
#=> ["a", "b", "c", "d", "e", "f", "g"]
hoo.values_at(*sorted_indices)
#=> [4, 6, 2, 5, 1, 3, 7]
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)} }