简体   繁体   English

PHP:如何随机选择最大概率值?

[英]PHP: How to select maximum probability value randomly?

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 .这将主要给出5,10而不是55

Total of value is 100% probability.值的总和是 100% 的概率。 Values can be in decimal as well like 55.55, 10.10, etc. but overall going to be 100%值可以是十进制的,也可以是 55.55、10.10 等,但总体来说是 100%

I already followed https://www.geeksforgeeks.org/how-to-get-random-value-out-of-an-array-in-php/我已经关注了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..所以结果可以是这样的:55、55、10、10、10、55、5等。

I found some useful link Generating random results by weight in PHP?我找到了一些有用的链接在 PHP 中按重量生成随机结果? where Probability = Weight其中概率 = 权重

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 .现在,您应该在 [0, 100) 之间生成一个随机数,我们称之为r

  • Now, if r lies between [0, 55), select the value 55.现在,如果r位于 [0, 55) 之间,则选择值 55。
  • else if r lies between [55, 55 + 10 = 65), select the value 10.否则,如果r位于 [55, 55 + 10 = 65) 之间,则选择值 10。
  • else if r lies between [65, 65 + 5 = 70), select the value 5.否则,如果r位于 [65, 65 + 5 = 70) 之间,则选择值 5。
  • else if r lies between [70, 70 + 5 = 75), select the value 5.否则,如果r位于 [70, 70 + 5 = 75) 之间,则选择值 5。
  • else if r lies between [75, 75 + 10 = 85), select the value 10.否则,如果r位于 [75, 75 + 10 = 85) 之间,则选择值 10。
  • else if r lies between [85, 85 + 10 = 95), select the value 10.否则,如果r位于 [85, 85 + 10 = 95) 之间,则选择值 10。
  • else if r lies between [95, 95 + 5 = 100), select the value 5.否则,如果r介于 [95, 95 + 5 = 100) 之间,则选择值 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: -因此,对于一般情况,如果您有一个名为“arr”的数组,则这是伪代码:-

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
}

Here is an implementation similar to roulette wheel selection in GA.这是一个类似于 GA 中轮盘选择的实现。 a version of the answer by EReload but bounded to the sum rather than a 100. EReload 的答案版本,但以总和而不是 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];
            }
        }
   }

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%值可以是十进制的,也可以是 55.55、10.10 等,但总体来说是 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.尽管您将总重量限制为 100,但您希望在该范围内容纳十进制值这一事实意味着您不能假设最多有 100 个单位可供选择。 If you have a granularity of tenths, then each unit to potentially pick from will be .1 .如果您的粒度为十分之一,那么每个可能从中挑选的单位将为.1 If specifying down to hundredths (like 55.55 ) then you will need a relative base unit of .01 at a time.如果指定为百分之几(如55.55 ),那么您一次需要一个相对基本单位为.01

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.因为我不想按浮点值进行迭代,所以我建议您将所有值按比例放大一个因子,以消除权重和随机数生成器中的所有浮点数 - 只需乘以 10/100/1000 即可,无论您需要什么将所有权重转换为整数。

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").选择一个介于 0 和((所有权重之和减去 1)乘以 10 的“最长代表十进制长度”的幂)之间的随机整数。
  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代码:( Demo ) -- 演示进行 10 次迭代以帮助揭示加权效果

$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

As i understand, you want higher number appear more frequently in rand method no matter how many times smaller number appear in your array.据我了解,无论数组中出现多少次较小的数字,您都希望在 rand 方法中更频繁地出现更高的数字。 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这里有一些不同 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为了获得价值,您将数组恢复为55 => [149]然后从随机中获取结果,并在恢复数组的值中再次随机

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.您可以做的是创建另一个包含 100 个数字的数组,代表总概率并在其中插入等于其值的数量,最后将其打乱以稍后随机选择索引。 Then you will get an array of 100 numbers where there most repeated number is the most probable.然后你会得到一个包含 100 个数字的数组,其中最重复的数字是最有可能的。 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;
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM