Both answers above rely on methods that will get slow quickly, especially the accepted one.
function weighted_random(items, weights) {
var i;
for (i = 0; i < weights.length; i++)
weights[i] += weights[i - 1] || 0;
var random = Math.random() * weights[weights.length - 1];
for (i = 0; i < weights.length; i++)
if (weights[i] > random)
break;
return items[i];
}
I replaced my older ES6 solution with this one as of December 2020, as ES6 isn’t supported in older browsers, and I personally think this one is more readable.
If you’d rather use objects with the properties item
and weight
:
function weighted_random(options) {
var i;
var weights = [];
for (i = 0; i < options.length; i++)
weights[i] = options[i].weight + (weights[i - 1] || 0);
var random = Math.random() * weights[weights.length - 1];
for (i = 0; i < weights.length; i++)
if (weights[i] > random)
break;
return options[i].item;
}
Explanation:
I’ve made this diagram that shows how this works:
This diagram shows what happens when an input with the weights [5, 2, 8, 3]
is given. By taking partial sums of the weights, you just need to find the first one that’s as large as a random number, and that’s the randomly chosen item.
If a random number is chosen right on the border of two weights, like with 7
and 15
in the diagram, we go with the longer one. This is because 0
can be chosen by Math.random
but 1
can’t, so we get a fair distribution. If we went with the shorter one, A
could be chosen 6 out of 18 times (0
, 1
, 2
, 3
, 4
), giving it a higher weight than it should have.