Looking for answer which works in all scenarios or for any number.
and
Values can be in decimal as well like 55.55, 10.10, etc. but overall going to be 100%
Although you are limiting the total weight to 100, the fact that you want to accommodate decimal values in that range means that you cannot assume a maximum of 100 units to pick from. If you have a granularity of tenths, then each unit to potentially pick from will be .1
. If specifying down to hundredths (like 55.55
) then you will need a relative base unit of .01
at a time.
Because I'd prefer not to iterate by float values, I recommend that you scale up all of your values by a factor that eliminates all floats in the weight and the random number generator -- simply multiply by 10/100/1000 whatever you need to convert all of the weights into integers.
Now to make the shortest work of the iteration process:
- Loop through your input array once to establish the longest decimal precision.
- Pick a random integer between 0 and ((the sum of all weights minus 1) multiplied by 10 to the power of "the longest representing decimal length").
- Loop through your input array again and simply check if the random integer is less than the current weight plus any previous weight(s); if not if so, break the loop and because the selected weighted, random number has been located.
Code: (Demo) -- the demo makes 10 iterations to aid in revealing the weighted effect
$valueWeights = [
149 => 55.555,
130 => 10.0050,
131 => 5,
132 => 5.2,
133 => 10,
134 => 10.24,
135 => 5
];
$mostDecimals = 0;
// not bothering to validate against infinite and extremely fringe case floats
foreach ($valueWeights as $value => $weight) {
$tempDecimals = 0;
while ((string)$weight !== (string)floor($weight)) {
$weight *= 10; // this is not permanently mutating the weight
++$tempDecimals;
}
$mostDecimals = max($mostDecimals, $tempDecimals);
}
echo "Most Decimals: {$mostDecimals}\n";
$factor = pow(10, $mostDecimals);
echo "Factor: " , $factor , "\n";
$totalWeight = (array_sum($valueWeights) - 1) * $factor;
for ($i = 0; $i < 10; ++$i) {
$rand = mt_rand(0, $totalWeight);
echo "\nRand: " , $rand , "\n";
$cumulativeScaledWeight = 0;
foreach ($valueWeights as $value => $weight) {
$cumulativeScaledWeight += $weight * $factor;
if ($rand < $cumulativeScaledWeight) {
echo "Value: {$value}\n";
break;
}
}
}
Output:
Most Decimals: 3
Factor: 1000
Rand: 52197
Value: 149
Rand: 33785
Value: 149
Rand: 4783
Value: 149
Rand: 24994
Value: 149
Rand: 76588
Value: 133
Rand: 77417
Value: 133
Rand: 40541
Value: 149
Rand: 80009
Value: 133
Rand: 14826
Value: 149
Rand: 52691
Value: 149