a) either use a stick...
An easy way to tackle this is to visualize your 180
as a stick. You want to break that stick into 3 pieces. So all you need to do is generate two non-equal rounded random values from 1 to 179 (the cutting points: one
& two
). And your random values will be:
a
: from zero to smallest value cutting point
b
: from smallest to largest value cutting points
c
: from largest value cutting point to 180
var Triangle = function() {
this.getRandom = function() {
// 1 - 179 random integer generator
return Math.round(Math.random() * (179 - 1) + 1);
}
this.one = this.getRandom();
this.two = this.getRandom();
while (this.one == this.two) {
// if equal, regenerate second - we don't want any segment to have 0 length
this.two = this.getRandom();
}
return {
'a': Math.min(this.one, this.two),
'b': Math.max(this.one, this.two) - Math.min(this.one, this.two),
'c': 180 - Math.max(this.one, this.two)
}
}
// Now let's make a few tests... how about 180 tests?
for (var i = 0; i < 180; i++) {
var t = new Triangle;
var div = document.createElement('div');
div.innerHTML = t.a + ' + ' + t.b + ' + ' + t.c + ' = ' +
(t.a + t.b + t.c);
document.getElementsByTagName('body')[0].appendChild(div);
}
div {
width: 33.3333%;
float: left;
padding: .5rem 0;
text-align: center;
}
b) ... or maths
While the above method is easy to visualize, and ensures each segment value has equal chances at being anywhere between 1
and 178
(total
- (parts
- 1 )), it's not particularly efficient from a programming point of view.
Each time one of the cutting points overlaps an existing one, it needs to be recalculated. In our case that would be quite rare but, given variable values for total
and parts
, the odds of it happening might differ.
Besides, we can totally avoid having to regenerate any value, thus saving computing power and, ultimately, the planet, or at least delaying its doom by an insignificant amount of time.
If we look at this from a mathematical point of view, we'll notice
- at least
1
part will be smaller than 61
((180 / (3 - 0)) + 1)
- at least
2
parts will be smaller than 91
((180 / (3 - 1)) + 1)
So, as a general rule, at least n
parts will be smaller than (total / (parts - (n - 1)) + 1)
. Now let's rewrite our method, generating the minimal amount or random numbers, in the correct range of possible values.
We also need to add as a last value the difference between total
and the sum of all previous values.
To make it more useful, I also considered total
and parts
as variables, so the method could be used to segment any total
number into any number of parts
.
var Segmentation = function (total, parts) {
this.random = function (min, max) {
return Math.round(Math.random() * (max - min) + min);
}
this.segments = [];
for (var i = 0; i < parts - 1; i++) {
this.segments.push(this.random(parts - i, total / parts + 1));
}
this.segments.push(total - this.segments.reduce((a, b) => a + b, 0));
return this.segments;
}
// let's test it
var h1 = document.createElement('h2');
h1.innerHTML = 'Triangles';
document.getElementsByTagName('body')[0].appendChild(h1);
for (var i = 0; i < 21; i++) {
var t = new Segmentation(180, 3),
div = document.createElement('div');
div.innerHTML = '';
for (var j = 0; j < t.length; j++) {
div.innerHTML += t[j] + (t.length - j > 1 ? ' + ' : ' = ');
}
div.innerHTML += t.reduce((a, b) => a + b, 0);
document.getElementsByTagName('body')[0].appendChild(div);
}
var h1 = document.createElement('h2');
h1.innerHTML = '<hr />Rectangles';
document.getElementsByTagName('body')[0].appendChild(h1);
for (var i = 0; i < 21; i++) {
var t = new Segmentation(360, 4),
div = document.createElement('div');
div.innerHTML = '';
for (var j = 0; j < t.length; j++) {
div.innerHTML += t[j] + (t.length - j > 1 ? ' + ' : ' = ');
}
div.innerHTML += t.reduce((a, b) => a + b, 0);
document.getElementsByTagName('body')[0].appendChild(div);
}
var h1 = document.createElement('h2');
h1.innerHTML = '<hr />Random segments';
document.getElementsByTagName('body')[0].appendChild(h1);
for (var i = 0; i < 21; i++) {
var total = Math.round(Math.random() * (2000 - 200) + 200),
parts = Math.round(Math.random() * (8 - 3) + 3),
t = new Segmentation(total, parts);
var div = document.createElement('div');
div.className = ('full');
div.innerHTML = '';
for (var j = 0; j < t.length; j++) {
div.innerHTML += t[j] + (t.length - j > 1 ? ' + ' : ' = ');
}
div.innerHTML += t.reduce((a, b) => a + b, 0);
document.getElementsByTagName('body')[0].appendChild(div);
}
div {
width: 33.3333%;
float: left;
padding: .5rem 0;
text-align: center;
}
div.full {
width: 100%;
text-align: initial;
}
Using this method, the first entry in the array has the biggest chances of having the smallest value while the last part has the biggest chances of having the highest value.
Note: Using this in a production environment is not recommended without sanitizing the input values.
Another note: To calculate the sum of all existing values in an array I used this awesome answer
's method.