I am trying to find all the combination of two arrays, but with an important variation:
Each value of the second array needs to be spread out over the val
I believe that your problem can be rephrased like this: generate all possible assignments of labels in left to the elements in right. This is a standard backtracking problem.
The number of solutions is l^r (since you can assign any of the labels to each element, independently), where l is the number of elements in left and r is the number of elements in right.
I'll provide a recursive solution, which is maybe you can rewrite in a non-recursive way, though it will not lower the complexity of the algorithm (maybe the constant). There is actually no way to lower the complexity since at some point you need to generate each solution. So do not test it for l=20 and r=20, try smaller numbers :p
// sol keeps the labels assigned to the elements in right
public static int[] sol = new int[r];
public static void generate(int p)
{
for (int i=0;i<l;i++)
{
sol[p] = i;
if (p==r-1)
{
// a solution is ready
outputSolution(sol);
}
else
{
// still generating a solution
generate(p+1);
}
}
}
// from where you need the results
generate(0);
I think I came up with one possible solution, but I’m certain it’s far from efficiënt.
Given the arrays:
left = [A, B, C]
right = [1, 2, 3]
First create a power set of right:
[]
[1]
[2]
[3]
[1, 2]
[1, 3]
[2, 3]
[1, 2, 3]
Then have nested loops through this for each value in left. Each loop will check if the value is not already in the previous loop and the last loop will also check if all values are present.
In psuedo this would look something like this:
for x in powerset
a = x
for y in powerset
if y not in x
b = y
for z in powerset
if z not in y and z not in x and [x + y + z] = right
c = z
displayresult
Edit
Here's this crappy inefficient solution in javascript. Posting it for completion sake.
https://jsfiddle.net/6o03d3L3/
function loop(left, right, powerSet, depth, siblings) {
for (var i=0; i<powerSet.length; i++) {
var values = powerSet[i];
var isValueUsed = false;
for (var k = 0; k < values.length; k++) {
for (var l = 0; l < siblings.length; l++) {
for (var m = 0; m < siblings[l].right.length; m++) {
if (values[k] === siblings[l].right[m]) {
isValueUsed = true;
break;
}
}
if (isValueUsed) {
break;
}
}
if (isValueUsed) {
break;
}
}
if (!isValueUsed) {
var result = { };
result.left = left[depth];
result.right = values;
var results = siblings.slice();
results.push(result);
if (depth < left.length - 1) {
loop(left, right, powerSet, depth + 1, results);
} else {
var valueCount = 0;
for (var j = 0; j < results.length; j++) {
valueCount += results[j].right.length;
}
if (valueCount == right.length) {
log(results);
}
}
}
}
}
Just for illustration, this is a version of @NinaScholz's
answer that doesn't use toString
for base conversion or
implement counting manually. I've kept the structure of the
code the same. values.length-i-1
could simply be `i', but
I wanted to keep the order of the output the same too.
var combine = function(names, values){
var result = [],
max = Math.pow(names.length, values.length),
m;
for(m=0; m<max; m+=1){
result.push(values.reduce(function(buckets, v, i){
var nidx = Math.floor(m / Math.pow(names.length, values.length-i-1)) % names.length;
buckets[names[nidx]].push(v);
return buckets;
}, names.reduce(function(a,v){
a[v] = [];
return a;
}, {})));
}
return result;
};
document.getElementById('out').innerHTML = JSON.stringify(
combine(Array.apply(null, Array(50)).map(function(_,i){ return i; }), [1,2]),
null,
4);
<pre id="out"></pre>
Edit: This version avoids possible problems with len = Math.pow(left.length, right.length)
and problems with length over 36 (cnr!).
combine(['A', 'B'], [1, 2, 3])
all possible rows are 2^3 = 8. In this example, the distribution is binary, but it changes the base with more parameters ofleft
.
distribution included in set
i c A B
----------------------------------------
0 0 0 0 { 1, 2, 3 } { }
1 0 0 1 { 1, 2 } { 3 }
2 0 1 0 { 1, 3 } { 2 }
3 0 1 1 { 1 } { 2, 3 }
4 1 0 0 { 2, 3 } { 1 }
5 1 0 1 { 2 } { 1, 3 }
6 1 1 0 { 3 } { 1, 2 }
7 1 1 1 { } { 1, 2, 3 }
The distribution
i = 3
of0 1 1
evaluates as:
- Take the first
0
and take it as index of leftleft[0] = A
and move the place value of 0 of rightright[0] = 1
to set A.- Take the second
1
and take it as index of leftleft[1] = B
and move the place value of 1 of rightright[1] = 2
to set B.- Take the third
1
and take it as index of leftleft[1] = B
and move the place value of 2 of rightright[2] = 3
to set B.
combine(['A', 'B', 'C'], [1, 2])
all possible rows are 3^2 = 9.
distribution included in set
i c A B C
----------------------------------------
0 0 0 { 1, 2 } { } { }
1 0 1 { 1 } { 2 } { }
2 0 2 { 1 } { } { 2 }
3 1 0 { 2 } { 1 } { }
4 1 1 { } { 1, 2 } { }
5 1 2 { } { 1 } { 2 }
6 2 0 { 2 } { } { 1 }
7 2 1 { } { 2 } { 1 }
8 2 2 { } { } { 1, 2 }
function combine(left, right) {
function carry() {
return c.reduceRight(function (r, _, i, o) {
return r && !(o[i] = (o[i] + 1) % left.length);
}, 1);
}
var c = Array.apply(null, { length: right.length }).map(function () { return 0; }),
result = [];
do {
result.push(c.reduce(function (r, a, i) {
r[left[a]].push(right[i]);
return r;
}, left.reduce(function (r, a) {
r[a] = [];
return r;
}, {})));
} while (!carry());
return result;
}
document.getElementById('out').innerHTML =
"combine(['A', 'B'], [1, 2, 3]) = " + JSON.stringify(combine(['A', 'B'], [1, 2, 3]), null, 4) + '\n'+
"combine(['A', 'B', 'C'], [1, 2] = " + JSON.stringify(combine(['A', 'B', 'C'], [1, 2]), null, 4) +'\n'+
"combine(['A', 'B', 'C'], [1, 2, 3] = " + JSON.stringify(combine(['A', 'B', 'C'], [1, 2, 3]), null, 4) +'\n'+
"combine(['A', 'B', 'C', 'D'], [1, 2, 3, 4, 5, 6]) = " + JSON.stringify(combine(['A', 'B', 'C', 'D'], [1, 2, 3, 4, 5, 6]), null, 4);
<pre id="out"></pre>
My suggestion is to try and code exactly what you are asking for:
function clone(arr) {
var copy = new Array(arr.length);
for (var i=0; i<arr.length; i++){
copy[i] = new Array();
for (var j=0; j<arr[i].length; j++)
copy[i][j] = arr[i][j];
}
return copy;
}
function f(left,right){
var result = [];
function _f(left,i){
if (i == right.length){
result.push(left);
return;
}
for (var j=0; j<left.length; j++){
var _left = clone(left);
_left[j].push(right[i]);
_f(_left,i + 1);
}
}
_f(left,0);
return result;
}
console.log(JSON.stringify(f([[],[]],[1,2,3])));
console.log(JSON.stringify(f([[],[],[]],[1,2,3])));
Output:
"[[[1,2,3],[]],[[1,2],[3]],[[1,3],[2]],[[1],[2,3]],[[2,3],[1]],[[2],[1,3]],[[3],[1,2]]
,[[],[1,2,3]]]"
"[[[1,2,3],[],[]],[[1,2],[3],[]],[[1,2],[],[3]],[[1,3],[2],[]],[[1],[2,3],[]]
,[[1],[2],[3]],[[1,3],[],[2]],[[1],[3],[2]],[[1],[],[2,3]],[[2,3],[1],[]],[[2],[1,3],[]]
,[[2],[1],[3]],[[3],[1,2],[]],[[],[1,2,3],[]],[[],[1,2],[3]],[[3],[1],[2]],[[],[1,3],[2]]
,[[],[1],[2,3]],[[2,3],[],[1]],[[2],[3],[1]],[[2],[],[1,3]],[[3],[2],[1]],[[],[2,3],[1]]
,[[],[2],[1,3]],[[3],[],[1,2]],[[],[3],[1,2]],[[],[],[1,2,3]]]"