I\'m trying to devise a (good) way to choose a random number from a range of possible numbers where each number in the range is given a weight. To put it simply: given the
This one is in Mathematica, but it's easy to copy to another language, I use it in my games and it can handle decimal weights:
weights = {0.5,1,2}; // The weights
weights = N@weights/Total@weights // Normalize weights so that the list's sum is always 1.
min = 0; // First min value should be 0
max = weights[[1]]; // First max value should be the first element of the newly created weights list. Note that in Mathematica the first element has index of 1, not 0.
random = RandomReal[]; // Generate a random float from 0 to 1;
For[i = 1, i <= Length@weights, i++,
If[random >= min && random < max,
Print["Chosen index number: " <> ToString@i]
];
min += weights[[i]];
If[i == Length@weights,
max = 1,
max += weights[[i + 1]]
]
]
(Now I'm talking with a lists first element's index equals 0) The idea behind this is that having a normalized list weights there is a chance of weights[n] to return the index n, so the distances between the min and max at step n should be weights[n]. The total distance from the minimum min (which we put it to be 0) and the maximum max is the sum of the list weights.
The good thing behind this is that you don't append to any array or nest for loops, and that increases heavily the execution time.
Here is the code in C# without needing to normalize the weights list and deleting some code:
int WeightedRandom(List weights) {
float total = 0f;
foreach (float weight in weights) {
total += weight;
}
float max = weights [0],
random = Random.Range(0f, total);
for (int index = 0; index < weights.Count; index++) {
if (random < max) {
return index;
} else if (index == weights.Count - 1) {
return weights.Count-1;
}
max += weights[index+1];
}
return -1;
}