Many of the answers on this page seem to use array aging, excessive iteration, a library, or a difficult-to-read process. Of course, everyone thinks that their own child is the cutest, but I honestly believe that my approach is poor, simple and easy to read / change ...
In OP, I will create an array of values (declared as keys) from 1 to 10, with 3, 4 and 5 with twice the weight of the other values (declared as values).
$values_and_weights=array( 1=>1, 2=>1, 3=>2, 4=>2, 5=>2, 6=>1, 7=>1, 8=>1, 9=>1, 10=>1 );
If you are going to make only one random choice and / or your array is relatively small * (make sure this is your own benchmarking), this is probably the best choice:
$pick=mt_rand(1,array_sum($values_and_weights)); $x=0; foreach($values_and_weights as $val=>$wgt){ if(($x+=$wgt)>=$pick){ echo "$val"; break; } }
This approach does not include modifying the array and probably won't need to iterate over the entire array (but it can).
On the other hand, if you intend to make more than one random selection in an array and / or your array is large enough * (make sure your own benchmarking), restructuring the array may be better.
The cost of memory to generate a new array will be increasingly justified as:
- array size increases and
- the number of random choices is increasing.
The new array requires replacing the "weight" with a "limit" for each value, adding the previous item weight to the current item weight.
Then flip the array so that the limits are the keys of the array, and the values are the values of the array. Logic: the selected value will have the lowest limit, which is> = $ pick.
// Declare new array using array_walk one-liner: array_walk($values_and_weights,function($v,$k)use(&$limits_and_values,&$x){$limits_and_values[$x+=$v]=$k;}); //Alternative declaration method - 4-liner, foreach() loop: /*$x=0; foreach($values_and_weights as $val=>$wgt){ $limits_and_values[$x+=$wgt]=$val; }*/ var_export($limits_and_values);
Creates this array:
array ( 1 => 1, 2 => 2, 4 => 3, 6 => 4, 8 => 5, 9 => 6, 10 => 7, 11 => 8, 12 => 9, 13 => 10, )
Now, to generate a random $pick and select a value:
// $x (from walk/loop) is the same as writing: end($limits_and_values); $x=key($limits_and_values); $pick=mt_rand(1,$x); // pull random integer between 1 and highest limit/key while(!isset($limits_and_values[$pick])){++$pick;} // smallest possible loop to find key echo $limits_and_values[$pick]; // this is your random (weighted) value
This approach is brilliant because isset() is very fast, and the maximum number of isset() calls in a while loop can only be up to the maximum weight (not to be confused with the limit) in the array. For this case, the maximum iteration = 2!
THIS APPROACH DOESN'T NEED TO LOSE THE WHOLE PAGE