Related
Say I have the following input:
inp = [2, 9, 3]
I need output as all tuples in mixed counting, like this:
outp = [[0, 0, 0], [0, 0, 1], [0, 0, 2], [0, 1, 0], [0, 1, 1], ..., [1, 8, 2]]
I know algorithm from Knuth vol 4a as direct loop solution, but I've heard ruby has some magic inside.
I am mostly C++ developer. My direct solution now looks like:
inparr = [2, 9, 3]
bmix = Array.new(inparr.size) { |i| 0 }
outp = Array.new
loop do
# some debug output
puts bmix.to_s
#visit next tuple
outp << bmix.clone
digit = inparr.size
while digit > 0 do
digit -= 1
if bmix[digit] + 1 < inparr[digit]
bmix[digit] += 1
break
end
bmix[digit] = 0
end
break if (bmix.select{|x| x != 0}.empty?)
end
How to rewrite it in several simple lines?
inp.
map { |i| (0...i).to_a }.
reduce(&:product).
map(&:flatten)
Used operations: Range, Enumerable#map, Enumerable#reduce, Array#product, Array#flatten.
You could use recursion.
def recurse(inp)
first, *rest = inp
rest.empty? ? [*0..first-1] : (0..first-1).flat_map do |e|
recurse(rest).map { |arr| [e, *arr] }
end
end
recurse [2, 4, 3]
#=> [[0, 0, 0], [0, 0, 1], [0, 0, 2],
# [0, 1, 0], [0, 1, 1], [0, 1, 2],
# [0, 2, 0], [0, 2, 1], [0, 2, 2],
# [0, 3, 0], [0, 3, 1], [0, 3, 2],
# [1, 0, 0], [1, 0, 1], [1, 0, 2],
# [1, 1, 0], [1, 1, 1], [1, 1, 2],
# [1, 2, 0], [1, 2, 1], [1, 2, 2],
# [1, 3, 0], [1, 3, 1], [1, 3, 2]]
If first, *rest = [2,4,3], then first #=> 2 and rest #=> [4,3].
See Enumerable#flat_map and Array#map. a ? b : c is called a ternery expression.
If e #=> 1 and arr #=> [2,1] then [e, *arr] #=> [1,2,1].
I will go to great lengths to avoid the use of Array#flatten. It's irrational, but to me it's an ugly method. That's usually possible using flat_map and/or the splat operator *.
Here's a mix of the 2 existing answers. It might be a bit more concise and readable:
head, *rest = inp.map{ |n| n.times.to_a }
head.product(*rest)
As an example:
inp = [2, 4, 3]
# => [2, 4, 3]
head, *rest = inp.map{ |n| n.times.to_a }
# => [[0, 1], [0, 1, 2, 3], [0, 1, 2]]
head.product(*rest)
# => [[0, 0, 0], [0, 0, 1], [0, 0, 2], [0, 1, 0], [0, 1, 1], [0, 1, 2], [0, 2, 0], [0, 2, 1], [0, 2, 2], [0, 3, 0], [0, 3, 1], [0, 3, 2], [1, 0, 0], [1, 0, 1], [1, 0, 2], [1, 1, 0], [1, 1, 1], [1, 1, 2], [1, 2, 0], [1, 2, 1], [1, 2, 2], [1, 3, 0], [1, 3, 1], [1, 3, 2]]
When I run the following in irb, it returns what I want. But when I run rspec -c intersection_spec.rb, it returns [[0,0]]. Why don't I get the desired results with rspec?
What am I doing wrong here?
intersection.rb
class Intersection
def self.create_arr(xa1, ya1, xa2, ya2)
((xa1.to_i)..(xa2.to_i)).to_a.product(((ya1.to_i)..(ya2.to_i)).to_a)
end
end
intersection_spec.rb
require './spec_helper'
require './intersection.rb'
describe Intersection do
#xa1 = 0.0
#ya1 = 0.0
#xa2 = 5.0
#ya2 = 5.0
#xb1 = 1.0
#yb1 = 1.0
#xb2 = 4.0
#yb2 = 4.0
specify{ expect(Intersection.create_arr(#xa1, #ya1, #xa2, #ya2)).to eq [[0,0],
[0,1], [0,2], [0,3], [0,4], [0,5], [1,0], [1,1], [1,2], [1,3], [1,4], [1,5],
[2,0], [2,1], [2,2], [2,3], [2,4], [2,5], [3,0], [3,1], [3,2], [3,3], [3,4],
[3,5], [4,0], [4,1], [4,2], [4,3], [4,4], [4,5], [5,0], [5,1], [5,2], [5,3],
[5,4], [5,5]] }
end
Run rspec.
rspec -c intersection_spec.rb
expected: [[0, 0], [0, 1], [0, 2], [0, 3], [0, 4], [0, 5], [1, 0], [1, 1],
[1, 2], [1, 3], [1, 4], [1, 5], [2, 0], [2, 1], [2, 2], [2, 3], [2, 4], [2, 5],
[3, 0], [3, 1], [3, 2], [3, 3], [3, 4], [3, 5], [4, 0], [4, 1], [4, 2], [4, 3],
[4, 4], [4, 5], [5, 0], [5, 1], [5, 2], [5, 3], [5, 4], [5, 5]]
got: [[0, 0]]
In irb
irb(main):029:0> #xa1 = 0.0
=> 0.0
irb(main):030:0> #ya1 = 0.0
=> 0.0
irb(main):031:0> #xa2 = 5.0
=> 5.0
irb(main):032:0> #ya2 = 5.0
=> 5.0
irb(main):033:0> #xb1 = 1.0
=> 1.0
irb(main):034:0> #yb1 = 1.0
=> 1.0
irb(main):035:0> #xb2 = 4.0
=> 4.0
irb(main):036:0> #yb2 = 4.0
=> 4.0
irb(main):037:0> def self.create_arr(xa1, ya1, xa2, ya2)
irb(main):038:1> ((xa1.to_i)..(xa2.to_i)).to_a.product(((ya1.to_i)..(ya2.to_i)).to_a)
irb(main):039:1> end
=> nil
irb(main):040:0> create_arr(#xa1, #ya1, #xa2, #ya2)
=> [[0, 0], [0, 1], [0, 2], [0, 3], [0, 4], [0, 5], [1, 0], [1, 1], [1, 2],
[1, 3], [1, 4], [1, 5], [2, 0], [2, 1], [2, 2], [2, 3], [2, 4], [2, 5], [3, 0],
[3, 1], [3, 2], [3, 3], [3, 4], [3, 5], [4, 0], [4, 1], [4, 2], [4, 3], [4, 4],
[4, 5], [5, 0], [5, 1], [5, 2], [5, 3], [5, 4], [5, 5]]
I can't say for sure why it fails your way, but I noticed it does work when you don't make the initial coordinates instance variables, so
#xa1 = 0.0
#ya1 = 0.0
#xa2 = 5.0
#ya2 = 5.0
becomes
xa1 = 0.0
ya1 = 0.0
xa2 = 5.0
ya2 = 5.0
then pass those into #create_array.
Supposing we have an array with this shape: [2, 5]. The possible index combinations are:
[
[0, 0],
[0, 1],
[0, 2],
[0, 3],
[0, 4],
[1, 0],
[1, 1],
[1, 2],
[1, 3],
[1, 4]
]
If the array has n dimension(s), is there a simple way to generate the indices in Ruby?
This should work:
def coordinates(first, *others)
(0...first).to_a.product(*others.map { |to| (0...to).to_a })
end
coordinates(2, 5)
#=> [[0, 0], [0, 1], [0, 2], [0, 3], [0, 4],
# [1, 0], [1, 1], [1, 2], [1, 3], [1, 4]]
coordinates(4, 3, 3)
#=> [[0, 0, 0], [0, 0, 1], [0, 0, 2],
# [0, 1, 0], [0, 1, 1], [0, 1, 2],
# [0, 2, 0], [0, 2, 1], [0, 2, 2],
# [1, 0, 0], [1, 0, 1], [1, 0, 2],
# [1, 1, 0], [1, 1, 1], [1, 1, 2],
# [1, 2, 0], [1, 2, 1], [1, 2, 2],
# [2, 0, 0], [2, 0, 1], [2, 0, 2],
# [2, 1, 0], [2, 1, 1], [2, 1, 2],
# [2, 2, 0], [2, 2, 1], [2, 2, 2],
# [3, 0, 0], [3, 0, 1], [3, 0, 2],
# [3, 1, 0], [3, 1, 1], [3, 1, 2],
# [3, 2, 0], [3, 2, 1], [3, 2, 2]]
Here you go. It may not be pretty, but it seems to work.
def gen_indices(dimensions, solutions = [], current=[], level = 0)
if level < dimensions.length
dimensions[level].times do |i|
current << i
if level == dimensions.length - 1
solutions << current.clone
else
gen_indices(dimensions, solutions, current, level + 1)
end
current.pop
end
end
solutions
end
p gen_indices([4,3,2])
Not sure this is totally correct, but it should help you on the right track:
def combinations(dimensions)
dimensions.inject([]){|total, dimension|
dim = (0...dimension).to_a
total.empty? ? dim : total.product(dim)
}.map(&:flatten)
end
p combinations([2, 5]) #=> [[0, 0], [0, 1], [0, 2], [0, 3], [0, 4], [1, 0], [1, 1], [1, 2], [1, 3], [1, 4]]
p combinations([2, 2, 2]) #=> [[0, 0, 0], [0, 0, 1], [0, 1, 0], [0, 1, 1], [1, 0, 0], [1, 0, 1], [1, 1, 0], [1, 1, 1]]
Try:
a = [[0,1],[0,1,2,3,4]]
res = a[0].product(a[1])
Given a set C with n elements (duplicates allowed) and a partition P of n
P = {i1, i2, ... / i1+i2+... = n}
how many different decompositions of C in subsets of size i1, i2, ... are there ?
Example :
C = {2 2 2 3}
P = {2 2}
C = {2 2} U {2 3}
P = {1 1 2}
C = {2} U {2} U {2 3}
C = {2} U {3} U {2 2}
P = {1 3}
C = {2} U {2 2 3}
C = {3} U {2 2 2}
I have a solution, but it is inefficient when C has more than a dozen of elements.
Thanks in advance
Philippe
The fact that the order of decomposition does not matter to you makes it much harder. That is, you are viewing {2 2} U {2 3} as the same as {2 3} U {2 2}. Still I have an algorithm that is better than what you have, but is not great.
Let me start it with a realistically complicated example. Our set will be A B C D E F F F F G G G G. The partition will be 1 1 1 1 2 2 5.
My first simplification will be to represent the information we care about in the set with the data structure [[2, 4], [5, 1]], meaning 2 elements are repeated 4 times, and 5 are repeated once.
My second apparent complication will be to represent the partition with [[5, 1, 1], [2, 2, 1], [4, 1, 1]. The pattern may not be obvious. Each entry is of the form [size, count, frequency]. So the one distinct instance of 2 partitions of size 2 turn into [2, 2, 1]. We're not using frequency yet, but it is counting distinguishable piles of the same size and commonness.
Now we're going to recurse as follows. We'll take the most common element, and find all of the ways to use it up. So in our case we take one of the piles of size 4, and find that we can divide it as follows, rearranging each remaining partition strategy in lexicographic order:
[4] leaving [[1, 1, 1], [2, 2, 1], [1, 4, 1]] = [[2, 2, 1], [1, 4, 1], [1, 1, 1]].
[3, [1, 0], 0] leaving [[2, 1, 1], [1, 1, 1], [2, 1, 1], [1, 4, 1]] = [[2, 1, 2], [1, 4, 1], [1, 1, 1]. (Note that we're now using frequency.)
[3, 0, 1] leaving [[2, 1, 1], [2, 2, 1], [0, 1, 1], [1, 3, 1]] = [[2, 2, 1], [2, 1, 1], [1, 3, 1]]
[2, [2, 0], 0] leaving [[3, 1, 1], [0, 1, 1], [2, 1, 1], [1, 4, 1]] = [[3, 1, 1], [2, 1, 1], [1, 4, 1]]
[2, [1, 1], 0] leaving [[3, 1, 1], [1, 2, 1], [1, 4, 1]] = [[3, 1, 1], [1, 4, 1], [1, 2, 1]]
[2, [1, 0], [1]] leaving [[3, 1, 1], [1, 1, 1], [2, 1, 1], [0, 1, 1], [1, 3, 1]] = [[3, 1, 1], [2, 1, 1], [1, 4, 1], [1, 1, 1]]
[2, 0, [1, 1]] leaving `[[3, 1, 1], [2, 2, 1], [0, 2, 1], [1, 2, 1]] = [[3, 1, 1], [2, 2, 1], [1, 2, 1]]1
[1, [2, 1]] leaving [[4, 1, 1], [0, 1, 1], [1, 1, 1], [1, 4, 1]] = [[4, 1, 1], [1, 4, 1], [1, 1, 1]]
[1, [2, 0], [1]] leaving [[4, 1, 1], [0, 1, 1], [2, 1, 1], [0, 1, 1], [1, 3, 1]] = [[4, 1, 1], [2, 1, 1], [1, 3, 1]]
[1, [1, 0], [1, 1]] leaving [[4, 1, 1], [1, 1, 1], [2, 1, 1], [0, 2, 1], [1, 2, 1]] = [[4, 1, 1], [2, 1, 1], [1, 2, 1], [1, 1, 1]]
[1, 0, [1, 1, 1]] leaving [[4, 1, 1], [2, 2, 1], [0, 3, 1], [1, 1, 1]] = [[4, 1, 1], [2, 2, 1], [1, 1, 1]]
[0, [2, 2]] leaving [[5, 1, 1], [0, 2, 1], [1, 4, 1]] = [[5, 1, 1], [1, 4, 1]]
[0, [2, 1], [1]] leaving [[5, 1, 1], [0, 1, 1], [1, 1, 1], [0, 1, 1], [1, 3, 1]] = [[5, 1, 1], [1, 3, 1], [1, 1, 1]]
[0, [2, 0], [1, 1]] leaving [[5, 1, 1], [0, 2, 1], [2, 1, 1], [0, 2, 1], [1, 2, 1]] = [[5, 1, 1], [2, 1, 1], [1, 2, 1]]
[0, [1, 1], [1, 1]] leaving [[5, 1, 1], [1, 2, 1], [0, 2, 1], [1, 2, 1]] = [[5, 1, 1,], [1, 2, 2]]
[0, [1, 0], [1, 1, 1]] leaving [[5, 1, 1], [1, 1, 1], [2, 1, 1], [0, 3, 1], [1, 1, 1]] = [[5, 1, 1], [2, 1, 1], [1, 1, 2]]
[0, 0, [1, 1, 1, 1]] leaving [[5, 1, 1], [2, 2, 1], [0, 4, 1]] = [[5, 1, 1], [2, 2, 1]]
Now each of those subproblems can be solved recursively. This may feel like we're on the way to constructing them all, but we aren't, because we memoize the recursive steps. It turns out that there are a lot of ways that the first two groups of 8 can wind up with the same 5 left overs. With memoization we don't need to repeatedly recalculate those solutions.
That said, we'll do better. Groups of 12 elements should not pose a problem. But we're not doing that much better. I wouldn't be surprised if it starts breaking down somewhere around groups of 30 or so elements with interesting sets of partitions. (I haven't coded it. It may be fine at 30 and break down at 50. I don't know where it will break down. But given that you're iterating over sets of partitions, at some fairly small point it will break down.)
All partitions can be found in 2 stages.
First: from P make new ordered partition of n, P_S={P_i1, P_i2, ..., P_ip}, summing identical i's.
P = {1, 1, 1, 1, 2, 2, 5}
P_S = (4, 4, 5)
Make partitions {C_i1, C_i2, ..., C_ip} of C with respect to P_S. Note, C_ix is multi-set like C. It is partitioning C into multi-sets by sizes of final partitions.
Second: for each {C_i1, C_i2, ..., C_ip} and for each ix, x={1,2,...,p} find number of partitions of C_ix into t (number of ix's in P) sets with ix elements. Call this number N(C_ix,ix,t).
Total number of partitions is:
sum by all {C_i1, C_i2, ..., C_ip} ( product N(C_ix,ix,t) ix={1,2,...,p} )
First part can be done recursively quite simple. Second is more complicated. Partition of multi-set M into n parts with k elements is same as finding all partially sorted list with elements from M. Partially order list is of type:
a_1_1, a_1_2, ..., a_1_k, a_2_1, a_2_2, ..., a_2_k, ....
Where a_i_x <= a_i_y if x < y and (a_x_1, a_x_2, ..., a_x_k) lexicographic <= (a_y_1, a_y_2, ..., a_y_k) if x < y. With these 2 conditions it is possible to create all partition from N(C_ix,ix,t) recursively.
For some cases N(C_ix,ix,t) is easy to calculate. Define |C_ix| as number of different elements in multi-set C_ix.
if t = 1 than 1
if |C_ix| = 1 than 1
if |C_ix| = 2 than (let m=minimal number of occurrences of elements in C_ix) floor(m/2) + 1
in general if |C_ix| = 2 than partition of m in numbers <= t.
I made a little code for creating a matrix of coordinates (like a chessboard), it's the following:
createMatrix(N,M,R) :- creaMatriu(N,M,A), reversed(R,A).
creaMatriu(N,0,[T]) :- creafila(N,0,T),!.
creaMatriu(N,M,[T|C]) :- creafila(N,M,T), M1 is M-1, creaMatriu(N,M1,C).
creafila(0,M,[[M,0]]):-!.
creafila(N,M,[[M,N]|C]) :-N1 is N-1,creafila(N1,M,C).
reversed(A, B) :- reversed(B, [], A).
reversed([A|B], C, D) :- reverse(N,A),reversed(B, [N|C], D).
reversed([], A, A).
The first time I executed it went well, but when i incremented the dimensions of the matrix, the "dots" at the end of the matrix begin to appear incrementing one coordinate as the dimension rises, as like this:
?- createMatrix(1,1,R).
R = [[[0, 0], [0, 1]], [[1, 0], [1, 1]]] .
?- createMatrix(2,1,R).
R = [[[0, 0], [0, 1], [0, 2]], [[1, 0], [1, 1], [1, 2]]] .
?- createMatrix(2,2,R).
R = [[[0, 0], [0, 1], [0, 2]], [[1, 0], [1, 1], [1, 2]], [[2, 0], [2, 1], [2, 2]]] .
?- createMatrix(3,2,R).
R = [[[0, 0], [0, 1], [0, 2], [0, 3]], [[1, 0], [1, 1], [1, 2], [1, 3]], [[2, 0], [2, 1], [2, 2], [2, 3]]] .
?- createMatrix(3,3,R).
R = [[[0, 0], [0, 1], [0, 2], [0, 3]], [[1, 0], [1, 1], [1, 2], [1, 3]], [[2, 0], [2, 1], [2, 2], [2, 3]], [[3, 0], [3, 1], [3, 2], [3|...]]] .
?- createMatrix(4,3,R).
R = [[[0, 0], [0, 1], [0, 2], [0, 3], [0, 4]], [[1, 0], [1, 1], [1, 2], [1, 3], [1, 4]], [[2, 0], [2, 1], [2, 2], [2, 3], [2|...]], [[3, 0], [3, 1], [3, 2], [3|...], [...|...]]] .
?- createMatrix(4,4,R).
R = [[[0, 0], [0, 1], [0, 2], [0, 3], [0, 4]], [[1, 0], [1, 1], [1, 2], [1, 3], [1, 4]], [[2, 0], [2, 1], [2, 2], [2, 3], [2|...]], [[3, 0], [3, 1], [3, 2], [3|...], [...|...]], [[4, 0], [4, 1], [4|...], [...|...]|...]] .
Anyone have any clue why this happens?
Thank you!
By default, the toplevel loop of SWI prints terms up to depth 10. Deeper parts are replaced by ... You can extend that depth or remove that limit by setting the depth to 0.
?- length(L,10).
L = [_A,_B,_C,_D,_E,_F,_G,_H,_I|...].
?- current_prolog_flag(toplevel_print_options,V).
V = [quoted(true),portray(true),max_depth(10),spacing(next_argument)].
?- set_prolog_flag(toplevel_print_options, [quoted(true), portray(true), max_depth(0), spacing(next_argument)]).
true.
?- length(L,10).
L = [_A,_B,_C,_D,_E,_F,_G,_H,_I,_J].
— update: in newer versions of SWI, another flag must be changed:
?- current_prolog_flag(T,V), atom_concat(_,options,T).
T = answer_write_options,
V = [quoted(true),portray(true),max_depth(10),spacing(next_argument)]
; true.
?- set_prolog_flag(answer_write_options, [quoted(true), portray(true), max_depth(0), spacing(next_argument)]).
true.