There are a lot of interesting cases, such as more bins than sum, more range than sum, only one bin, etc etc.
For most of those, the solution is to allow numbers to go negative in order to satisfy the constraints of the distribution asked for. So, if I ask for two bins with a sum of 0 and a random range of 10, then {-5, 5} would be a valid solution. For efficiency 1 bin and 0 bins are special cases.
My general approach would be a three-pass solution.
- pre-populate the array with the mean. There may be leftover, but we can anticipate exactly how many cells will be mean+1 in a single pass.
int mean = sum/binCount;
int left = sum%binCount;
for(int i=0; i<binCount; i++){
bins[i] = i<left ? mean+1 : mean;
}
- iterate over pairs, and carry value between them, conserving the sum. The list sum is valid when this loop starts, and after each operation it is still valid – one entry goes up, the other goes down.
for(int i=1; i<binCount; i=i+2){
int alloc = (int)random(-carry, carry+1);
bins[i-1] = bins[i-1] - alloc;
bins[i] = bins[i] + alloc;
}
- optionally, shuffle the result – this will produce many potential orders that would not be possible from pairs.
return shuffleIntArray(bins);
What I like about this is that it is reasonably quick and really robust – you can throw anything at it and it produces a reasonable output from the space of all possible solutions, and it could produce (I believe) any possible solution to a given request for a distribution.
void setup() {
println(binify(20, 5, 1));
println(binify(20, 5, 5));
println(binify(20, 5, 10));
println(binify(20, 5, 20));
println(binify(20, 5, 100));
println(binify(20, 5, 0));
println(binify(5, 5, 1));
println(binify(5, 5, 2));
println(binify(5, 5, 5));
println(binify(5, 5, 10));
println(binify(5, 5, 0));
println(binify(0, 5, 10));
println(binify(0, 5, 2));
println(binify(0, 5, 0));
println(binify(10, 1, 100));
println(binify(10, 0, 0));
}
int[] binify (int sum, int binCount, int carry) {
println("binify: sum ", sum, " binCount ", binCount, " carry ", carry);
if(binCount==0) return null;
if(binCount==1) return new int[]{sum};
int[] bins = new int[binCount];
int mean = sum/binCount;
int left = sum%binCount;
for(int i=0; i<binCount; i++){
bins[i] = i<left ? mean+1 : mean;
}
for(int i=1; i<binCount; i=i+2){
int alloc = (int)random(-carry, carry+1);
bins[i-1] = bins[i-1] - alloc;
bins[i] = bins[i] + alloc;
}
return shuffleIntArray(bins);
}
int[] shuffleIntArray (int[] list) {
IntList shuffler = new IntList(list);
shuffler.shuffle();
for (int i=0; i<list.length; i++) {
list[i] = shuffler.get(i);
}
return list;
}
To speed it up you could drop the shuffling step – that will constrain the output, but it will still appear semi-random for some purposes.
You might also be able to squeeze some more speed out by combining steps 1 and 2 into a single for loop.