function improve(groups) {
var pairs = [[0,1],[0,2],[0,3],[0,4],[0,5],[1,2],[1,3],[1,4],[1,5],[2,3],[2,4],[2,5],[3,4],[3,5],[4,5]];
var combi = [[0,9,14],[0,10,13],[0,11,12],[1,6,14],[1,7,13],[1,8,12],[2,5,14],[2,7,11],[2,8,10],[3,5,13],[3,6,11],[3,8,9],[4,5,12],[4,6,10],[4,7,9]];
// FIND OPTIMAL DISTRIBUTION FOR ALL PAIRS OF GROUPS
var optim = [];
for (var i = 0; i < 15; i++) {
optim[i] = optimise(groups[pairs[i][0]], groups[pairs[i][1]]);
}
// FIND BEST COMBINATION OF PAIRS
var best, score = -1;
for (var i = 0; i < 15; i++) {
var current = optim[combi[i][0]].score + optim[combi[i][1]].score + optim[combi[i][2]].score;
if (current > score) {
score = current;
best = i;
}
}
// REDISTRIBUTE STUDENTS INTO GROUPS AND RETURN NEW SCORE
groups[0] = optim[combi[best][0]].group1.slice();
groups[1] = optim[combi[best][0]].group2.slice();
groups[2] = optim[combi[best][1]].group1.slice();
groups[3] = optim[combi[best][1]].group2.slice();
groups[4] = optim[combi[best][2]].group1.slice();
groups[5] = optim[combi[best][2]].group2.slice();
return score;
}
// FIND OPTIMAL DISTRIBUTION FOR PAIR OF GROUPS
function optimise(group1, group2) {
var optim = {group1: [], group2: [], score: -1};
var set = group1.concat(group2).sort(function(a, b) {return a - b});
var distr = [0,0,0,0,0,1,1,1,1,1,1];
// TRY EVERY COMBINATION
do {
// KEEP FIRST STUDENT IN FIRST GROUP TO AVOID SYMMETRIC COMBINATIONS
var groups = [[set[0]], []];
// DISTRIBUTE STUDENTS INTO GROUP 0 OR 1 ACCORDING TO BINARY ARRAY
for (var j = 0; j < 11; j++) {
groups[distr[j]].push(set[j + 1]);
}
// CHECK SCORE OF GROUPS AND STORE IF BETTER
var score = quality(groups[0]) + quality(groups[1]);
if (score > optim.score) {
optim.group1 = groups[0].slice();
optim.group2 = groups[1].slice();
optim.score = score;
}
} while (increment(distr));
return optim;
// GENERATE NEXT PERMUTATION OF BINARY ARRAY
function increment(array) {
var digit = array.length, count = 0;
while (--digit >= 0) {
if (array[digit] == 1) ++count
else if (count) {
array[digit] = 1;
for (var i = array.length - 1; i > digit; i--) {
array[i] = --count > 0 ? 1 : 0;
}
return true;
}
}
return false;
}
}
// SCORE FOR ONE GROUP ; RANGE: 0 ~ 8.958797346140275
function quality(group) {
// LOGARITHM FAVOURS SMALL IMPROVEMENTS TO ALL GROUPS OVER LARGE IMPROVEMENT TO ONE GROUP
return Math.log((group[5] - group[4]) * (group[4] - group[3]) * (group[3] - group[2]) * (group[2] - group[1]) * (group[1] - group[0]));
}
// SUM OF SCORES FOR ALL 6 GROUPS ; RANGE: 0 ~ 53.75278407684165
function overallQuality(groups) {
var score = 0;
for (var i = 0; i < 6; i++) score += quality(groups[i]);
return score;
}
// PREPARE RANDOM TEST DATA
var students = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36];
var groups = [[],[],[],[],[],[]];
for (var i = 5; i >=0; i--) {
for (var j = 5; j >= 0; j--) {
var pick = Math.floor(Math.random() * (i * 6 + j));
groups[i].push(students[pick]);
students[pick] = students[i * 6 + j];
}
groups[i].sort(function(a, b) {return a - b});
}
// DISPLAY INITIAL SCORE AND DISTRIBUTION
var score = overallQuality(groups);
document.write("<PRE>Initial: " + score.toFixed(2) + " " + JSON.stringify(groups) + "<BR>");
// IMPROVE DISTRIBUTION UNTIL SCORE NO LONGER INCREASES
var prev, step = 0;
do {
prev = score;
score = improve(groups);
document.write("Step " + ++step + " : " + score.toFixed(2) + " " + JSON.stringify(groups) + "<BR>");
} while (score > prev && score < 53.75278407684165);
if (score >= 53.75278407684165) document.write("Optimal solution reached.</PRE>");