Creating random weight results in PHP?

I know how to generate a random number in PHP, but lets say that I want a random number between 1-10, but I want more than 3,4,5, and then 8,9,10. How is this possible? I would publish what I tried, but honestly, I don’t even know where to start.

+46
php random
Jan 15 '09 at 0:44
source share
12 answers

Based on the @Allain answer / link , I worked on this quick function in PHP. You will need to change it if you want to use non-integer weighting.

/** * getRandomWeightedElement() * Utility function for getting random values with weighting. * Pass in an associative array, such as array('A'=>5, 'B'=>45, 'C'=>50) * An array like this means that "A" has a 5% chance of being selected, "B" 45%, and "C" 50%. * The return value is the array key, A, B, or C in this case. Note that the values assigned * do not have to be percentages. The values are simply relative to each other. If one value * weight was 2, and the other weight of 1, the value with the weight of 2 has about a 66% * chance of being selected. Also note that weights should be integers. * * @param array $weightedValues */ function getRandomWeightedElement(array $weightedValues) { $rand = mt_rand(1, (int) array_sum($weightedValues)); foreach ($weightedValues as $key => $value) { $rand -= $value; if ($rand <= 0) { return $key; } } } 
+79
Aug 08 2018-12-12T00:
source share

For an effective random number, beveled sequentially to one end of the scale:

  • Choose a continuous random number between 0..1
  • Climb the degree γ to evade it. 1 is unweighted, the bottom gives more higher numbers and vice versa
  • Scale to the desired range and round to the nearest whole

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)); 
+27
Jan 15 '09 at 1:45
source share

There's a good tutorial for you .

Basically:

  • Sum the weight of all numbers.
  • Choose a random number less
  • subtract the weights in order until the result becomes negative, and return this number, if any.
+21
Jan 15 '09 at 0:52
source share

A naive hack for this would be to create a list or an array of type

1, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 6, 6, 7, 7, 7, 7, 8, 8, 9, 9, 10, 10

And then randomly select from it.

+11
Jan 15 '09 at 0:48
source share

This tutorial allows you to go through it, in PHP, with several cut and paste solutions. Please note that this procedure is slightly modified from what you find on this page, as a result of the comment below.

Function taken from the message:

 /** * weighted_random_simple() * Pick a random item based on weights. * * @param array $values Array of elements to choose from * @param array $weights An array of weights. Weight must be a positive number. * @return mixed Selected element. */ function weighted_random_simple($values, $weights){ $count = count($values); $i = 0; $n = 0; $num = mt_rand(1, array_sum($weights)); while($i < $count){ $n += $weights[$i]; if($n >= $num){ break; } $i++; } return $values[$i]; } 
+4
May 19, '10 at 19:24
source share

Simple and fair. Just copy / paste and test it.

 /** * Return weighted probability * @param (array) prob=>item * @return key */ function weightedRand($stream) { $pos = mt_rand(1,array_sum(array_keys($stream))); $em = 0; foreach ($stream as $k => $v) { $em += $k; if ($em >= $pos) return $v; } } $item['30'] = 'I have more chances than everybody :]'; $item['10'] = 'I have good chances'; $item['1'] = 'I\'m difficult to appear...'; for ($i = 1; $i <= 10; $i++) { echo weightedRand($item).'<br />'; } 

Edit: Added missing bracket at the end.

+2
May 01 '12 at 2:48
source share

You can use weightedChoice from the PHP Custom Library . It takes a list of pairs (item, weight) to be able to work with elements that cannot be arrays. You can use pairs to convert array(item => weight) to the desired format.

 use function \nspl\a\pairs; use function \nspl\rnd\weightedChoice; $weights = pairs(array( 1 => 10, 2 => 15, 3 => 15, 4 => 15, 5 => 15, 6 => 10, 7 => 5, 8 => 5, 9 => 5, 10 => 5 )); $number = weightedChoice($weights); 

In this example, 2-5 will appear 3 times more often than 7-10.

+2
Dec 25 '15 at 16:36
source share

Since I used the IainMH solution, I can also 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> 
+1
Mar 20 '12 at 21:19
source share
 /** * @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 a very easy way. How to get a random weighted item. I populate the variable with the $ key variable. I get $ key for the $ weight x array. After that use array_rand for the array. And I have a random value;).

+1
Jun 24 '16 at 7:26
source share

I just released a class to easily do weighted sorting .

It is based on the same algorithm as Brad's and Allain's answers, and is optimized for speed, unit testing for uniform distribution and supports elements of any type of PHP.

The use is simple. Create it:

 $picker = new Brick\Random\RandomPicker(); 

Then add the elements as an array of weighted values ​​(only if your elements are strings or integers):

 $picker->addElements([ 'foo' => 25, 'bar' => 50, 'baz' => 100 ]); 

Or use individual addElement() calls. This method supports any types of PHP values ​​as elements (strings, numbers, objects, ...), in contrast to the array approach:

 $picker->addElement($object1, $weight1); $picker->addElement($object2, $weight2); 

Then we get a random element:

 $element = $picker->getRandomElement(); 

The probability of obtaining one of the elements depends on its associated weight. The only limitation is that weights must be integers.

0
Apr 01 '16 at 12:46 on
source share

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 changed this from the answer here Selecting a random item by custom weights

After I wrote this, I saw that someone else had a more elegant answer. He is he.

0
Jul 31 '16 at 14:04
source share

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

0
Apr 17 '17 at 7:19
source share



All Articles