Generating random results by weight in PHP?

前端 未结 12 711
青春惊慌失措
青春惊慌失措 2020-11-22 07:35

I know how to generate a random number in PHP but lets say I want a random number between 1-10 but I want more 3,4,5\'s then 8,9,10\'s. How is this possible? I would post wh

相关标签:
12条回答
  • 2020-11-22 08:03

    There's a pretty good tutorial for you.

    Basically:

    1. Sum the weights of all the numbers.
    2. Pick a random number less than that
    3. subtract the weights in order until the result is negative and return that number if it is.
    0 讨论(0)
  • 2020-11-22 08:03

    function getBucketFromWeights($values) { $total = $currentTotal = $bucket = 0;

    foreach ($values as $amount) {
        $total += $amount;
    }
    
    $rand = mt_rand(0, $total-1);
    
    foreach ($values as $amount) {
        $currentTotal += $amount;
    
        if ($rand => $currentTotal) {
            $bucket++;
        }
        else {
            break;
        }
    }
    
    return $bucket;
    

    }

    I ugh modified this from an answer here Picking random element by user defined weights

    After I wrote this I saw someone else had an even more elegant answer. He he he he.

    0 讨论(0)
  • 2020-11-22 08:04

    Since I used IainMH's solution, I may as well share my PHP code:

    <pre><?php
    
    // Set total number of iterations
    $total = 1716;
    
    // Set array of random number
    $arr = array(1, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 5);
    $arr2 = array(0, 0, 1, 1, 2, 2, 2, 3, 3, 4, 5);
    
    // Print out random numbers
    for ($i=0; $i<$total; $i++){
    
        // Pick random array index
        $rand = array_rand($arr);
        $rand2 = array_rand($arr2);
    
        // Print array values
        print $arr[$rand] . "\t" . $arr2[$rand2] . "\r\n";
    
    }
    
    ?></pre>
    
    0 讨论(0)
  • 2020-11-22 08:04

    Many of the answers on this page seem to use array bloating, excessive iteration, a library, or a hard-to-read process. Of course, everyone thinks their own baby is the cutest, but I honestly think my approach is lean, simple and easy to read/modify...

    Per the OP, I will create an array of values (declared as keys) from 1 to 10, with 3, 4, and 5 having double 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 only going to make one random selection and/or your array is relatively small* (do your own benchmarking to be sure), this is probably your best bet:

    $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 involves no array modification and probably won't need to iterate the entire array (but may).


    On the other hand, if you are going to make more than one random selection on the array and/or your array is sufficiently large* (do your own benchmarking to be sure), restructuring the array may be better.

    The cost in memory for generating a new array will be increasingly justified as:

    1. array size increases and
    2. number of random selections increases.

    The new array requires the replacement of "weight" with a "limit" for each value by adding the previous element's weight to the current element's weight.

    Then flip the array so that the limits are the array keys and the values are the array values. The logic is: the selected value will have the lowest limit that 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 the random $pick and select the 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 the while loop can only be as many as the largest weight (not to be confused with limit) in the array. For this case, maximum iterations = 2!

    THIS APPROACH NEVER NEEDS TO ITERATE THE ENTIRE ARRAY

    0 讨论(0)
  • 2020-11-22 08:05
    /**
     * @param array $weightedValues
     * @return string
     */
    function getRandomWeightedElement(array $weightedValues)
    {
        $array = array();
    
        foreach ($weightedValues as $key => $weight) {
            $array = array_merge(array_fill(0, $weight, $key), $array);
        }
    
        return $array[array_rand($array)];
    }
    

    getRandomWeightedElement(array('A'=>10, 'B'=>90));

    This is very easy method. How get random weighted element. I fill array variable $key. I get $key to array $weight x. After that, use array_rand to array. And I have random value ;).

    0 讨论(0)
  • 2020-11-22 08:13

    For an efficient random number skewed consistently towards one end of the scale:

    • Choose a continuous random number between 0..1
    • Raise to a power γ, to bias it. 1 is unweighted, lower gives more of the higher numbers and vice versa
    • Scale to desired range and round to integer

    eg. in PHP (untested):

    function weightedrand($min, $max, $gamma) {
        $offset= $max-$min+1;
        return floor($min+pow(lcg_value(), $gamma)*$offset);
    }
    echo(weightedrand(1, 10, 1.5));
    
    0 讨论(0)
提交回复
热议问题