4

I have below array & code

    $a = [
      149 => 55,
      130 => 10,
      131 => 5,
      132 => 5,
      133 => 10,
      134 => 10,
      135 => 5
    ];

   $rand = rand (0,(count($a)-1));

   echo array_values($a)[$rand];

This will give majorly result as 5,10 instead of 55.

Total of value is 100% probability. Values can be in decimal as well like 55.55, 10.10, etc. but overall going to be 100%

I already followed https://www.geeksforgeeks.org/how-to-get-random-value-out-of-an-array-in-php/

But this is not giving perfect result as expected.

So which has highest probability should be selected majorly and randomly.

So result can be like this : 55, 55, 10, 10, 10, 55, 5, etc..

I found some useful link Generating random results by weight in PHP? where Probability = Weight

Jackson
  • 1,426
  • 3
  • 27
  • 60

5 Answers5

10

Right now, your array is this: -

55, 10, 5, 5, 10, 10, 5

Now, you should generate a random number between [0, 100), let's call it r.

  • Now, if r lies between [0, 55), select the value 55.
  • else if r lies between [55, 55 + 10 = 65), select the value 10.
  • else if r lies between [65, 65 + 5 = 70), select the value 5.
  • else if r lies between [70, 70 + 5 = 75), select the value 5.
  • else if r lies between [75, 75 + 10 = 85), select the value 10.
  • else if r lies between [85, 85 + 10 = 95), select the value 10.
  • else if r lies between [95, 95 + 5 = 100), select the value 5.

I am sure you would have got the idea...

So, for the general case, if you have an array named 'arr', this is the pseudocode: -

function SELECTPROB()
{
    $r = generateRandomNumber(0, 100);    //function to generate random number between 0 and 100, (100 exclusive)
    $sum = 0;
    foreach($arr as $i)
    {
        if($r >= $sum && $r < $sum + $i)
        {
            return $i
        }
        $sum = $sum + $i
    }
    return -1    //Should technically never reach upto this, but it can if your probability's sum is not 100
}
EReload
  • 199
  • 2
  • 12
  • 1
    @AntonyJack You mentioned in your question **So which has highest probability should be selected majorly and randomly.** Here, 55 is selected majorly and randomly – EReload Mar 18 '20 at 10:59
  • 1
    This solution is rather vague. The general idea is good, but you only provide pseudocode for this one specific case. The OP mentioned that values can differ, meaning the array is not static. Assume different values and a varying number of elements and design a solution for that if you want to improve your answer. – El_Vanja Mar 18 '20 at 11:23
  • 1
    Actually @El_Vanja I disagree, I think this is a perfectly good answer. It explains the underlying reasoning and gives a pseudocode "implementation" to provide clarity on what the actual code should look like. This should be plenty enough for the original asker to create a solution for their specific case. Posting a code sample that solves the _exact_ question posed (to the point where it could be copied and pasted) would be spoonfeeding, and would be unnecessary, or perhaps even detrimental to the answer as far as I'm concerned. – David Z Mar 21 '20 at 06:28
  • @DavidZ Fair enough. But are you talking about the original form of the answer or the edited one? When I think about it again, sure, even the original form should suffice. I stand corrected there. But I still feel that this pseudocode general solution that was added improves the answer - as you yourself said, adds clarity (within the tagged language). My comment wasn't explicit and that's a mistake on my part, but I never meant that the answerer shoud actually write a concrete code sample that solves this. I aimed for pseudocode. – El_Vanja Mar 21 '20 at 11:51
  • @El_Vanja Ah, I didn't realize it had been edited, sorry. I was talking about revision 2. But I do also think some of what I said applies to the original version as well, in particular that the original was a perfectly decent answer. – David Z Mar 21 '20 at 22:13
  • I like the conciseness, the simplicity and elegance of the code, and using only one loop, this makes it also efficient. – santamanno Mar 27 '20 at 18:57
1

Here is an implementation similar to roulette wheel selection in GA. a version of the answer by EReload but bounded to the sum rather than a 100.

    $a = [
      149 => 55,
      130 => 10,
      131 => 5,
      132 => 5,
      133 => 10,
      134 => 10,
      135 => 5
    ];

   echo randSelect($a);

   function randSelect($a) {
        $values = array_values($a);
        $sum = array_sum($values);
        $rand = (rand(0,1000)/1000) * $sum;
        $partialSum = 0;

        for ($i=0; $i < count($values); $i++) {
            $partialSum += $values[$i];
            if($partialSum >= $rand){
                return $values[$i];
                // incase you are using something like array_count_values and are actually looking for the keys
                // return array_keys($a)[$i];
            }
        }
   }
David
  • 69
  • 4
0

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:

  1. Loop through your input array once to establish the longest decimal precision.
  2. 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").
  3. 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
mickmackusa
  • 43,625
  • 12
  • 83
  • 136
0

As i understand, you want higher number appear more frequently in rand method no matter how many times smaller number appear in your array. You need unique your array first.

Random by sum weight is simple method for random, but you can control weight more freely by sum power instead of itself.

$a = [
      149 => 55,
      130 => 10,
      131 => 5,
      132 => 5,
      133 => 10,
      134 => 10,
      135 => 5
    ];

$val_arr = array_unique(array_values($a));

function rand_by_sum($arr, $power=1){
        $sum = 0;
        $f_val = function($f)use($power){
                return pow($f, $power);
        };
        foreach($arr as $f){
                $sum += $f_val($f);
        }
        $rand = mt_rand(0, $sum);

        $tmp_sum = 0;
        foreach($arr as $f){
                $tmp_sum += $f_val($f);
                if($tmp_sum >= $rand) return $f;
        }
}

for($i=0; $i< 10; $i++){
        echo rand_by_sum($val_arr, $argv[1]) . " ";
}

echo "\n";

And here some test result with different pow

php test.php 0.5
55 5 10 55 5 55 55 5 55 55 

php test.php 2
55 55 10 55 55 55 55 55 55 55 

php test.php 1
55 10 55 55 55 55 55 55 55 10

To get value, you revert array as 55 => [149] then get result from random, and random again in values of reverted array

takid1412
  • 296
  • 4
  • 8
0

I think you could actually shuffle the array and pop an element, shuffle again and pop the element, that would be randomly and those numbers with greater probability would be first.

What you can do is to create another array with 100 numbers, representing the total probability and inserting in it the amount of numbers equal to its value, finally you shuffle it to pick an index random later. Then you will get an array of 100 numbers where there most repeated number is the most probable. Lastly you just have to pick a random index and create your array.

can you tell me if you are looking for something like this or if I am misunderstanding the problem

function getProb($array, $elements)
{
    $myNewArray = [];
    $myProbabilisticArray = $this->getProbabilisticArray($array);
    for ($i=0; $i < $elements; $i++) {
        $myNewArray[] = $myProbabilisticArray[array_rand($myProbabilisticArray)];
    }
    return $myNewArray;
}

function getProbabilisticArray($array) {
    $myNewArray = [];
    rsort($array);

    $currentProbability = 0;
    $accumulatedProbability = $array[0];
    $currentPosition = 0;

    while ($currentProbability < 100) {
        if ($currentProbability > $accumulatedProbability) {
            $currentPosition++;
            $accumulatedProbability += $array[$currentPosition];
        }
        array_push($myNewArray, $array[$currentPosition]);
        $currentProbability++;
    }
    shuffle($myNewArray);
    return $myNewArray;
}
marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
DiMiGi
  • 11
  • 2