Creating permutations from a multi-dimensional array in Ruby - ruby

I have the following multi-dimensional array in Ruby:
[[1,2], [3], [4,5,6]]
I need to have the following output:
[[1,3,4], [1,3,5], [1,3,6], [2,3,4], [2,3,5], [2,3,6]]
I have tried creating a recursive function, but I'm not having much luck.
Are there any Ruby functions that would help with this? Or is the only option to do it recursively?
Thanks

Yup, Array#product does just that (Cartesian product):
a = [[1,2], [3], [4,5,6]]
head, *rest = a # head = [1,2], rest = [[3], [4,5,6]]
head.product(*rest)
#=> [[1, 3, 4], [1, 3, 5], [1, 3, 6], [2, 3, 4], [2, 3, 5], [2, 3, 6]]
Another variant:
a.inject(&:product).map(&:flatten)
#=> [[1, 3, 4], [1, 3, 5], [1, 3, 6], [2, 3, 4], [2, 3, 5], [2, 3, 6]]

Related

Extracting non-contiguous sets

I want to get all non-contiguous sets (i.e. any subset whose elements can't be adjacent in the original set):
go([1,2,3,4,5]) => [1],[1,3,5],[1,3],[1,4],[1,5],[2],[2,4],[2,5],[3]
I am fairly close with:
def go(ns)
return [[]] if ns == [] || ns == nil
return [[ns[0]]] if ns.length < 3
(0..ns.length-1).to_a.map do |i|
dup = go(ns[i+2..-1])
dup.map do |a|
[ns[i]] + a
end
end
end
This gives:
[[[1, [3, 5]], [1, [4]], [1, [5]]], [[2, 4]], [[3, 5]], [[4]], [[5]]]
which is close to the right results (misses [1], I just have the concat of the arrays messed up and can't figure out how to flatten them.
Note the elements can be any numbers, they are just unique numbers in no order, they could be for example
[35, 40, 100, 54, 13]
For a given n > 1 I have computed an array of all arrays containing one or more integers of the form [a, ... , b] where a >= 1, b <= n and for each adjacent pairs of element i, j, j > i+1. If n = 1, [[1]] is returned.
I've done this using recursion. recurse(m,n) computes all such arrays whose first element is m.
Code
def doit(n)
(1..n).reduce([]) { |a,m| a + recurse(m,n) }
end
def recurse(m,n)
return [[m]] if m >= n-1
(m+2..n).reduce([[m]]) { |a,p| a + recurse(p,n).map { |b| [m]+b } }
end
Examples
doit 6
#=> [[1], [1, 3], [1, 3, 5], [1, 3, 6], [1, 4], [1, 4, 6], [1, 5], [1, 6],
# [2], [2, 4], [2, 4, 6], [2, 5], [2, 6],
# [3], [3, 5], [3, 6], [4], [4, 6], [5], [6]]
doit 8
#=> [[1], [1, 3], [1, 3, 5], [1, 3, 5, 7], [1, 3, 5, 8], [1, 3, 6],
# [1, 3, 6, 8], [1, 3, 7], [1, 3, 8], [1, 4], [1, 4, 6], [1, 4, 6, 8],
# [1, 4, 7], [1, 4, 8], [1, 5], [1, 5, 7], [1, 5, 8], [1, 6],
# [1, 6, 8], [1, 7], [1, 8],
# [2], [2, 4], [2, 4, 6], [2, 4, 6, 8], [2, 4, 7], [2, 4, 8], [2, 5],
# [2, 5, 7], [2, 5, 8], [2, 6], [2, 6, 8], [2, 7], [2, 8],
# [3], [3, 5], [3, 5, 7], [3, 5, 8], [3, 6], [3, 6, 8], [3, 7], [3, 8],
# [4], [4, 6], [4, 6, 8], [4, 7], [4, 8],
# [5], [5, 7], [5, 8],
# [6], [6, 8],
# [7],
# [8]]
Explanation
I believe the clearest way for me to explain how the recursion works—especially for readers with limited experience using recursion—is to simply execute the code after it has been salted with puts statements. I've indented whenever the method calls itself and outdented whenever the method returns.
INDENT = 6
#pos = 0
def indent; #pos += INDENT; #s = ' '*#pos; end
def outdent; #pos -= INDENT; #s = ' '*#pos; end
def doit(n)
puts "doit: n=#{n}"
(1..n).reduce([]) do |a,m|
puts "a=#{a}"
puts "calling recurse(#{m},#{n})"
indent
a + recurse(m,n)
end
end
def recurse(m,n)
puts "\n#{#s}entered recurse(#{m},#{n})"
if m >= n-1
puts "#{#s}returning #{[[m]]} as m >= n-1\n\n"
outdent
return [[m]]
end
puts "#{#s}begin reduce"
a = (m+2..n).reduce([[m]]) do |a,p|
puts "#{#s} p=#{p}, a=#{a}"
puts "#{#s} calling recurse(#{p},#{n})"
indent
arr = recurse(p,n).map { |b| [m]+b }
puts "#{#s} back to recurse(#{m},#{n}) from recurse(#{p},#{n})"
puts "#{#s} array returned mapped to #{arr}"
a + arr
end
puts "#{#s}return #{a} from recurse(#{m},#{n})\n\n"
outdent
a
end
doit 6
doit: n=6
a=[]
calling recurse(1,6)
entered recurse(1,6)
begin reduce
p=3, a=[[1]]
calling recurse(3,6)
entered recurse(3,6)
begin reduce
p=5, a=[[3]]
calling recurse(5,6)
entered recurse(5,6)
returning [[5]] as m >= n-1
back to recurse(3,6) from recurse(5,6)
array returned mapped to [[3, 5]]
p=6, a=[[3], [3, 5]]
calling recurse(6,6)
entered recurse(6,6)
returning [[6]] as m >= n-1
back to recurse(3,6) from recurse(6,6)
array returned mapped to [[3, 6]]
return [[3], [3, 5], [3, 6]] from recurse(3,6)
back to recurse(1,6) from recurse(3,6)
array returned mapped to [[1, 3], [1, 3, 5], [1, 3, 6]]
p=4, a=[[1], [1, 3], [1, 3, 5], [1, 3, 6]]
calling recurse(4,6)
entered recurse(4,6)
begin reduce
p=6, a=[[4]]
calling recurse(6,6)
entered recurse(6,6)
returning [[6]] as m >= n-1
back to recurse(4,6) from recurse(6,6)
array returned mapped to [[4, 6]]
return [[4], [4, 6]] from recurse(4,6)
back to recurse(1,6) from recurse(4,6)
array returned mapped to [[1, 4], [1, 4, 6]]
p=5, a=[[1], [1, 3], [1, 3, 5], [1, 3, 6], [1, 4], [1, 4, 6]]
calling recurse(5,6)
entered recurse(5,6)
returning [[5]] as m >= n-1
back to recurse(1,6) from recurse(5,6)
array returned mapped to [[1, 5]]
p=6, a=[[1], [1, 3], [1, 3, 5], [1, 3, 6], [1, 4], [1, 4, 6], [1, 5]]
calling recurse(6,6)
entered recurse(6,6)
returning [[6]] as m >= n-1
back to recurse(1,6) from recurse(6,6)
array returned mapped to [[1, 6]]
return [[1], [1, 3], [1, 3, 5], [1, 3, 6], [1, 4], [1, 4, 6], [1, 5],
[1, 6]] from recurse(1,6)
a=[[1], [1, 3], [1, 3, 5], [1, 3, 6], [1, 4], [1, 4, 6], [1, 5], [1, 6]]
calling recurse(2,6)
entered recurse(2,6)
begin reduce
p=4, a=[[2]]
calling recurse(4,6)
entered recurse(4,6)
begin reduce
p=6, a=[[4]]
calling recurse(6,6)
entered recurse(6,6)
returning [[6]] as m >= n-1
back to recurse(4,6) from recurse(6,6)
array returned mapped to [[4, 6]]
return [[4], [4, 6]] from recurse(4,6)
back to recurse(2,6) from recurse(4,6)
array returned mapped to [[2, 4], [2, 4, 6]]
p=5, a=[[2], [2, 4], [2, 4, 6]]
calling recurse(5,6)
entered recurse(5,6)
returning [[5]] as m >= n-1
back to recurse(2,6) from recurse(5,6)
array returned mapped to [[2, 5]]
p=6, a=[[2], [2, 4], [2, 4, 6], [2, 5]]
calling recurse(6,6)
entered recurse(6,6)
returning [[6]] as m >= n-1
back to recurse(2,6) from recurse(6,6)
array returned mapped to [[2, 6]]
return [[2], [2, 4], [2, 4, 6], [2, 5], [2, 6]] from recurse(2,6)
a=[[1], [1, 3], [1, 3, 5], [1, 3, 6], [1, 4], [1, 4, 6], [1, 5], [1, 6],
[2], [2, 4], [2, 4, 6], [2, 5], [2, 6]]
calling recurse(3,6)
entered recurse(3,6)
begin reduce
p=5, a=[[3]]
calling recurse(5,6)
entered recurse(5,6)
returning [[5]] as m >= n-1
back to recurse(3,6) from recurse(5,6)
array returned mapped to [[3, 5]]
p=6, a=[[3], [3, 5]]
calling recurse(6,6)
entered recurse(6,6)
returning [[6]] as m >= n-1
back to recurse(3,6) from recurse(6,6)
array returned mapped to [[3, 6]]
return [[3], [3, 5], [3, 6]] from recurse(3,6)
a=[[1], [1, 3], [1, 3, 5], [1, 3, 6], [1, 4], [1, 4, 6], [1, 5], [1, 6],
[2], [2, 4], [2, 4, 6], [2, 5], [2, 6], [3], [3, 5], [3, 6]]
calling recurse(4,6)
entered recurse(4,6)
begin reduce
p=6, a=[[4]]
calling recurse(6,6)
entered recurse(6,6)
returning [[6]] as m >= n-1
back to recurse(4,6) from recurse(6,6)
array returned mapped to [[4, 6]]
return [[4], [4, 6]] from recurse(4,6)
a=[[1], [1, 3], [1, 3, 5], [1, 3, 6], [1, 4], [1, 4, 6], [1, 5], [1, 6],
[2], [2, 4], [2, 4, 6], [2, 5], [2, 6], [3], [3, 5], [3, 6], [4], [4, 6]]
calling recurse(5,6)
entered recurse(5,6)
returning [[5]] as m >= n-1
a=[[1], [1, 3], [1, 3, 5], [1, 3, 6], [1, 4], [1, 4, 6], [1, 5], [1, 6],
[2], [2, 4], [2, 4, 6], [2, 5], [2, 6], [3], [3, 5], [3, 6], [4], [4, 6], [5]]
calling recurse(6,6)
entered recurse(6,6)
returning [[6]] as m >= n-1
#=> [[1], [1, 3], [1, 3, 5], [1, 3, 6], [1, 4], [1, 4, 6], [1, 5], [1, 6],
# [2], [2, 4], [2, 4, 6], [2, 5], [2, 6], [3], [3, 5], [3, 6], [4], [4, 6],
# [5], [6]]

How to get 'fair combination' from an array of n elements?

Using combination method on Ruby,
[1, 2, 3, 4, 5, 6].combination(2).to_a
#=> [[1, 2], [1, 3], [1, 4], [1, 5], [1, 6], [2, 3],
# [2, 4], [2, 5], [2, 6], [3, 4], [3, 5], [3, 6],
# [4, 5], [4, 6], [5, 6]]
we can get a 2-dimensional array having 15 (6C2) elements.
I would like to create a fair_combination method that returns an array like this:
arr = [[1, 2], [3, 5], [4, 6],
[3, 4], [5, 1], [6, 2],
[5, 6], [1, 3], [2, 4],
[2, 3], [4, 5], [6, 1],
[1, 4], [2, 5], [3, 6]]
So that every three sub-arrays (half of 6) contain all the given elements:
arr.each_slice(3).map { |a| a.flatten.sort }
#=> [[1, 2, 3, 4, 5, 6],
# [1, 2, 3, 4, 5, 6],
# [1, 2, 3, 4, 5, 6],
# [1, 2, 3, 4, 5, 6],
# [1, 2, 3, 4, 5, 6]]
This makes it kind of "fair", by using as different elements as possible as arrays go on.
To make it more general, what it needs to satisfy is as follows:
(1) As you follow the arrays from start and count how many times each number appears, at any point it should be as flat as possible;
(1..7).to_a.fair_combination(3)
#=> [[1, 2, 3], [4, 5, 6], [7, 1, 4], [2, 5, 3], [6, 7, 2], ...]
The first 7 numbers make [1,2,...,7] and so do the following 7 numbers.
(2) Once number A comes in the same array with B, A does not want to be in the same array with B if possible.
(1..10).to_a.fair_combination(4)
#=> [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 1, 5], [2, 6, 9, 3], [4, 7, 10, 8], ...]
Is there any good algorithm that creates a "fair combination" like this ?
It's not guaranteed to give the best solution, but it gives a good enough one.
At each step, it chooses a minimal subpool which is the set of items of minimal height, for which there is still a combination to choose from (height is the number of times the items have been used before).
For instance, let the enumerator be
my_enum = FairPermuter.new('abcdef'.chars, 4).each
The first iteration may return
my_enum.next # => ['a', 'b', 'c', 'd']
At this point those letters have height 1, but there is not enough letters of height 0 to make a combination, so take just all of them for the next:
my_enum.next # => ['a', 'b', 'c', 'e'] for instance
Now the heights are 2 for a, b and c, 1 for d and e, and 0 for f, and still the optimal pool is the full initial set.
So this is not really optimized for combinations of large size. On the other side, if the size of the combination is at most half of the size of the initial set, then the algorithm is pretty decent.
class FairPermuter
def initialize(pool, size)
#pool = pool
#size = size
#all = Array(pool).combination(size)
#used = []
#counts = Hash.new(0)
#max_count = 0
end
def find_valid_combination
[*0..#max_count].each do |height|
candidates = #pool.select { |item| #counts[item] <= height }
next if candidates.size < #size
cand_comb = [*candidates.combination(#size)] - #used
comb = cand_comb.sample
return comb if comb
end
nil
end
def each
return enum_for(:each) unless block_given?
while combination = find_valid_combination
#used << combination
combination.each { |k| #counts[k] += 1 }
#max_count = #counts.values.max
yield combination
return if #used.size >= [*1..#pool.size].inject(1, :*)
end
end
end
Results for fair combinations of 4 over 6
[[1, 2, 4, 6], [3, 4, 5, 6], [1, 2, 3, 5],
[2, 4, 5, 6], [2, 3, 5, 6], [1, 3, 5, 6],
[1, 2, 3, 4], [1, 3, 4, 6], [1, 2, 4, 5],
[1, 2, 3, 6], [2, 3, 4, 6], [1, 2, 5, 6],
[1, 3, 4, 5], [1, 4, 5, 6], [2, 3, 4, 5]]
Results of fair combination of 2 over 6
[[4, 6], [1, 3], [2, 5],
[3, 5], [1, 4], [2, 6],
[4, 5], [3, 6], [1, 2],
[2, 3], [5, 6], [1, 6],
[3, 4], [1, 5], [2, 4]]
Results of fair combinations of 2 over 5
[[4, 5], [2, 3], [3, 5],
[1, 2], [1, 4], [1, 5],
[2, 4], [3, 4], [1, 3],
[2, 5]]
Time to get combinations of 5 over 12:
1.19 real 1.15 user 0.03 sys
Naïve implementation would be:
class Integer
# naïve factorial implementation; no checks
def !
(1..self).inject(:*)
end
end
class Range
# constant Proc instance for tests; not needed
C_N_R = -> (n, r) { n.! / ( r.! * (n - r).! ) }
def fair_combination(n)
to_a.permutation
.map { |a| a.each_slice(n).to_a }
.each_with_object([]) do |e, memo|
e.map!(&:sort)
memo << e if memo.all? { |me| (me & e).empty? }
end
end
end
▶ (1..6).fair_combination(2)
#⇒ [
# [[1, 2], [3, 4], [5, 6]],
# [[1, 3], [2, 5], [4, 6]],
# [[1, 4], [2, 6], [3, 5]],
# [[1, 5], [2, 4], [3, 6]],
# [[1, 6], [2, 3], [4, 5]]]
▶ (1..6).fair_combination(3)
#⇒ [
# [[1, 2, 3], [4, 5, 6]],
# [[1, 2, 4], [3, 5, 6]],
# [[1, 2, 5], [3, 4, 6]],
# [[1, 2, 6], [3, 4, 5]],
# [[1, 3, 4], [2, 5, 6]],
# [[1, 3, 5], [2, 4, 6]],
# [[1, 3, 6], [2, 4, 5]],
# [[1, 4, 5], [2, 3, 6]],
# [[1, 4, 6], [2, 3, 5]],
# [[1, 5, 6], [2, 3, 4]]]
▶ Range::C_N_R[6, 3]
#⇒ 20
Frankly, I do not understand how this function should behave for 10 and 4, but anyway this implementation is too memory consuming to work properly on big ranges (on my machine it gets stuck on ranges of size > 8.)
To adjust this to more robust solution one needs to get rid of permutation there in favor of “smart concatenate permuted arrays.”
Hope this is good for starters.

Ruby array product with asterisk

I was studying how to list out all divisors of a number and came across this solution by Marc-Andre here. In his solution, there is one part of the code which does something like this:
array.product(*arrays_of_array) # the asterisk seems to have done sth.
I tried it in irb to try play around but I couldn't make sense of the outputs. I tried:
a=[0,1,2]
b=[3,4]
c=[[5,6],[7,8]]
I understand that array.product(other_array) is a method to list all combinations of the two arrays into one. With this knowledge, I tested out several experiments
a.product(b) => [[0, 3], [0, 4], [1, 3], [1, 4], [2, 3], [2, 4]] / 6 elements
a.product(*b) => TypeError: no implicit conversion of Fixnum into Array
a.product(c) => [[0, [5, 6]], [0, [7, 8]], [1, [5, 6]], [1, [7, 8]], [2, [5, 6]], [2, [7, 8]]] / 6 elements
a.product(*c) => [[0, 5, 7], [0, 5, 8], [0, 6, 7], [0, 6, 8], [1, 5, 7], [1, 5, 8], [1, 6, 7], [1, 6, 8], [2, 5, 7], [2, 5, 8], [2, 6, 7], [2, 6, 8]]
From observation, It seems the asterisk (*) has to be applied to a multi-dimensional array? (i.e. matrix?). Without the asterisk, the product returns 6 elements and the combinations only one level. While with the asterisk, the combination will go 1 level deeper and returns 12 elements, and combine until there is no array within the combinations. Where can I find more examples to study this behaviour of the asterisk?
Edit:
I tried to introduce one more variable
d=[[[9,0],[1,2]],[[3,4],[5,6]]]
a.product(*d) => [[0, [9, 0], [3, 4]], [0, [9, 0], [5, 6]], [0, [1, 2], [3, 4]], [0, [1, 2], [5, 6]], [1, [9, 0], [3, 4]], [1, [9, 0], [5, 6]], [1, [1, 2], [3, 4]], [1, [1, 2], [5, 6]], [2, [9, 0], [3, 4]], [2, [9, 0], [5, 6]], [2, [1, 2], [3, 4]], [2, [1, 2], [5, 6]]]
So the asterisk sign only makes it go one level deeper.
In the context of finding the list of divisors. Can anyone explain what the code exactly does?
require 'prime'
def factors_of(number)
primes, powers = number.prime_division.transpose
exponents = powers.map{|i| (0..i).to_a}
divisors = exponents.shift.product(*exponents).map do |powers|
primes.zip(powers).map{|prime, power| prime ** power}.inject(:*)
end
divisors.sort.map{|div| [div, number / div]}
end
p factors_of(4800) # => [[1, 4800], [2, 2400], ..., [4800, 1]]
*(splat) is used to expand collections.
In your example, with b = [3,4],
a.product(*b)
is equivalent to
a.product(3, 4)
which generates an error because Array#product expects an Array as argument, not two integers.

Creating pairs from an Array?

Is there a simple way to create pairs from an array?
For example, if I have an array [1,2,3,4] how would I go about trying to return this array?
[[1,2], [1,3], [1,4], [2,1], [2,3], [2,4], [3,1], [3,2], [3,4], [4,1], [4,2], [4,3]]
Every element is paired with every other other element except itself, and duplicates are allowed.
You can use Array#permutation for this:
[1,2,3,4].permutation(2).to_a
# => [[1, 2], [1, 3], [1, 4], [2, 1], [2, 3], [2, 4], [3, 1], [3, 2], [3, 4], [4, 1], [4, 2], [4, 3]]
[1,2,3,4].permutation(2).map{ |n| "(#{ n.join(",") })" }
# => ["(1,2)", "(1,3)", "(1,4)", "(2,1)", "(2,3)", "(2,4)", "(3,1)", "(3,2)", "(3,4)", "(4,1)", "(4,2)", "(4,3)"]

How to reshape a Ruby array

Having an array
a = [1, 2, 3, 4, 5, 6]
I want to reshape it to
a = [[1, 2], [3, 4], [5, 6]]
I've had an impression that there was a specific method for this. I've just been through Array class reference, but failed to find it. Does anyone remember?
You can do something like this:
a = [1, 2, 3, 4, 5, 6]
a.each_slice(2).to_a # => [[1, 2], [3, 4], [5, 6]]
Like this, for example:
a = [1, 2, 3, 4, 5, 6]
a.each_slice(2).to_a # => [[1, 2], [3, 4], [5, 6]]

Resources