If you only need the number of ways a group of 9 people can be divided into 3 subgroups of 2, 3 and 4 people each then that's easy to compute mathematically using C
(the function to calculate the number of combinations).
- First you have 9 people out of which you need to select 2 people. Hence you do
C(9, 2)
.
- Next you have 7 people out of which you need to select 3 people. Hence you do
C(7, 3)
.
- Finally you have 4 people out of which you need to select 4 people. Hence you do
C(4, 4)
. However C(n, n)
is always 1.
Hence the number of ways to divide a group of 9 people into 3 subgroups of 2, 3 and 4 people is C(9, 2) * C(7, 3) * C(4, 4)
. This can be simplified to C(9, 2) * C(7, 3)
, which is 36 * 35
which is 1260
.
We can write a function to compute this for us:
function ways(n) {
var l = arguments.length, w = 1;
for (var i = 1; i < l; i++) {
var m = arguments[i];
w *= combinations(n, m);
n -= m;
}
return w;
}
To make this function work we need to define the function combinations
:
function combinations(n, k) {
return factorial(n) / factorial(n - k) / factorial(k);
}
Finally we need to define the function for factorial
:
function factorial(n) {
var f = n;
while (--n) f *= n;
return f;
}
Then we compute the number of ways as follows:
alert(ways(9, 2, 3)); // 1260
You can see the demo here: http://jsfiddle.net/bHSuh/
Note that we didn't need to specify the last subgroup of 4 people because that is implied.
However I believe that you want to generate each possible way. This is the sort of thing that the amb
operator is perfect for. So the first thing we'll do is write the amb
operator in JavaScript:
function amb(options, callback) {
var length = options.length;
for (var i = 0; i < length; i++) {
try {
callback(options[i]); // try the next option
return; // no problem, quit
} catch (e) {
continue; // problem, next
}
}
throw new Error("amb tree exhausted"); // throw a tantrum
}
Next we'll write a function which picks a given set of items from a list of indices:
function pick(list, items) {
var length = list.length, selected = [], rest = [];
for (var i = 0; i < length; i++) {
if (items.indexOf(i) < 0) rest.push(list[i]);
else selected.push(list[i]);
}
return [selected, rest];
}
We also need a function which will generate a list of indices:
function getIndices(length) {
var indices = [];
for (var i = 0; i < length; i++)
indices.push(i);
return indices;
}
Finally we'll implement the group
function recursively:
function group(options, divisions) {
var subgroup = [], groups = [], n = 0;
var indices = getIndices(options.length);
var division = divisions.shift(), remaining = divisions.length;
try {
amb(indices, select);
} catch (e) {
return groups;
}
function select(index) {
subgroup.push(index);
if (++n < division) {
try { amb(indices.slice(index + 1), select); }
catch (e) { /* we want to continue processing */ }
} else {
var subgroups = pick(options, subgroup);
if (remaining) {
var children = group(subgroups.pop(), divisions.slice());
var length = children.length;
for (var i = 0; i < length; i++)
groups.push(subgroups.concat(children[i]));
} else groups.push(subgroups);
}
n--;
subgroup.pop();
throw new Error;
}
}
Now you can use it as follows:
var groups = group([
"aldo", "beat", "carla",
"david", "evi", "flip",
"gary", "hugo", "ida"
], [2, 3]);
Notice again that you didn't need to specify the last subgroup of 4 people since it's implied.
Now let's see whether the output is as we expected it to be:
console.log(groups.length === ways(9, 2, 3)); // true
There you go. There are exactly 1260 ways that a group of 9 people can be divided into 3 subgroups of 2, 3 and 4 people each.
Now I know that my group
function looks a little daunting but it's actually really simple. Try to read it and understand what's going on.
Imagine that you're the boss of 9 people. How would you divide them into 3 subgroups of 2, 3 and 4 people? That's exactly the way my group
function works.
If you still can't understand the logic after a while then I'll update my answer and explain the group
function in detail. Best of luck.
BTW I just realized that for this problem you don't really need amb
. You may simply use forEach
instead. The resulting code would be faster because of the absence of try-catch blocks:
function group(options, divisions) {
var subgroup = [], groups = [], n = 0;
var indices = getIndices(options.length);
var division = divisions.shift(), remaining = divisions.length;
indices.forEach(select);
return groups;
function select(index) {
subgroup.push(index);
if (++n < division) indices.slice(index + 1).forEach(select);
else {
var subgroups = pick(options, subgroup);
if (remaining) {
var children = group(subgroups.pop(), divisions.slice());
var length = children.length;
for (var i = 0; i < length; i++)
groups.push(subgroups.concat(children[i]));
} else groups.push(subgroups);
}
subgroup.pop();
n--;
}
}
Since we don't use amb
anymore the execution time of the program has decreased tenfold. See the result for yourself: http://jsperf.com/amb-vs-foreach
Also I've finally created a demo fiddle of the above program: http://jsfiddle.net/Ug6Pb/