按权重随机选择值的PHP代码

4

我将要创建一个“抽奖系统”。

请看我的表格:

userid-lottaryid-amount
1 -------- 1 ----  1
2 -------- 1 ---- 10
3 -------- 1 ---- 15
4 -------- 1 ---- 20

我希望选择一个获胜者和第二名。

但是我不能随机选择获胜者,因为第四个用户有20张彩票,而第一个用户只有一张。所以我需要通过权重生成随机结果,以使比赛更加公平。

我找到了下面的php函数,但我不知道如何使用它。

      function weighted_random_simple($values, $weights){ 
      $count = count($values); 
      $i = 0; 
      $n = 0; 
      $num = mt_rand(0, array_sum($weights)); 

      while($i < $count){
          $n += $weights[$i]; 
          if($n >= $num){
              break; 
          }
          $i++; 
      } 
      return $values[$i]; 

  }

    $values = array('1', '10', '20', '100');
    $weights = array(1, 10, 20, 100);

    echo weighted_random_simple($values, $weights);

我需要将userid列作为数组获取到$values中,将amount列获取到$weights中。但是我无法完成。

以下是我目前的代码:

    $query = $handler->prepare("SELECT 

      `cvu`.`lottaryid` as `lottaryid`, 
      `cvu`.`userid` as `userid`, 
      `cvu`.`amount` as `amount`, 

      `members`.`id` as `members_memberid`, 
      `members`.`username` as `username`

      FROM `lottariesandmembers` as `cvu`

      LEFT JOIN `members` as `members` ON `cvu`.`userid` = `members`.`id`  WHERE `cvu`.`lottaryid` = 2");
    $query->bindParam(':lottaryid', $lottaryid, PDO::PARAM_INT);
    $query->execute();



    while($r = $query->fetch()) {

        for ( $count=1 ; $count <= $r["amount"] ; $count++ ) {

            $abcprint = "$r[userid].$count - $r[username] - <br>";

            echo "$abcprint";

        }


    } 

我有一段代码,只会按照用户数量重复列出用户。例如:

1.1 user1
2.1 user2
2.2 user2
2.3 user2
..
2.10 user2
3.1 user3
..
3.15 user3
4.1 user4
..
4.20 user4

等等等等。。。但我不知道如何从列表中选择获胜者。

如果您愿意帮助我,我想合并这些代码并创建这个小脚本。

如果您有其他解决方案,我也很乐意进行头脑风暴。


你需要打印用户列表*金额吗,还是这只是试图找到获胜者的副作用? - Don't Panic
副作用。我在想我可以从那个列表中选择获胜者。 - Rough
4个回答

2
这并不是非常优雅,但对于较小的彩票应该可以使用。它只是构建了一个庞大的数组,并随机选择一个元素。可以把它想象成一个装满小纸片的巨大帽子。每个持有者都有他们的份额和编号的小纸片。例如,十张纸片上写着持有者名字为“a”,20张纸片上写着“b”等等...
<?php

$holder_totals = array(
    'a' => '10',
    'b' => '20',
    'c' => '20',
    'd' => '50'
);

$big_hat = array();
foreach($holder_totals as $holder_id => $total) {
    $holder_hat = array_fill(0, intval($total), $holder_id);
    $big_hat    = array_merge($big_hat, $holder_hat);
}

// Drum roll
foreach (range(1,4) as $n) {
    $random_key = array_rand($big_hat);
    printf("Winner %d is %s.\n", $n, $big_hat[$random_key]);
    unset($big_hat[$random_key]); // Remove winning slip
}

样例输出:

Winner 1 is d.
Winner 2 is c.
Winner 3 is d.
Winner 4 is b.

Big hat看起来像这样:

Array
(
    [0] => a
    [1] => a
    [2] => a
    [3] => a
    [4] => a
    [5] => a
    [6] => a
    [7] => a
    [8] => a
    [9] => a
    [10] => b
    [11] => b
    [12] => b
    [13] => b
    [14] => b
    ... and so on...
)

2

不要像你现在这样打印值,你可以构建一个大数组,然后从该数组中随机选择一个值。

while($r = $query->fetch()) {
    for ( $i=0; $i <= $r["amount"]; $i++ ) {
        // Add the user into the array as many times as they have tickets
        $tickets[] = $r['userid'];
    }
}

// select the first place winner
$first = $tickets[mt_rand(0, count($tickets) - 1)];

// remove the first place winner from the array
$tickets = array_values(array_filter($tickets, function($x) use ($first) { 
    return $x != $first; 
}));

// select the second place winner
$second = $tickets[mt_rand(0, count($tickets) - 1)];

我相信有一种更高效的使用数学的方法,但我需要再想一想...


厉害了,我离整天想做的事情很近了。我只是回显 $first$second。当它们返回不同的用户ID时,它们是有效的,但有时我会得到错误 Notice: Undefined offset: 22 in C:\www\lottary\test.php on line 35 而不是 $second。第35行是 $tickets = array_filter($tickets, function($x) use ($first) { return $x != $first; }); 我不理解那里的 $x。可能与错误有关。再次感谢! - Rough
啊,我忘记了使用array_filter时键是被保留的。我想这就是原因。我进行了轻微的编辑,使用array_values来修复它。 - Don't Panic
非常感谢您,这个方法非常有效。顺便说一下,我刚刚通过print_r($tickets);查看了数组。结果有点吓人,总共有135个数组。第一个只有一个值,第二个有两个,以此类推,第135个有135个值。我现在担心性能问题,因为我正在测试的总数是131。而且这还很小。另外一件事是赔率。因为在第113个数组之前,不可能看到第四个用户。非常感谢,我会点赞的 :) - Rough

2
  /**
   * 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;
      }
    }
  }

这里有一个高效灵活的函数。但是如果你想使用非整数权重,你需要修改它。


1
你可以使用我的库nspl中的weightedChoice函数。
use function \nspl\rnd\weightedChoice;

// building your query here

$pairs = [];
while($r = $query->fetch()) {
    $pairs[] = [$r['userid'], $r['amount']];
}

$winnerId = weightedChoice($pairs);

您可以使用Composer安装该库:
composer require ihor/nspl

或者您可以直接从GitHub上重用weightedChoice代码:

/**
 * Returns a random element from a non-empty sequence of items with associated weights
 *
 * @param array $weightPairs List of pairs [[item, weight], ...]
 * @return mixed
 */
function weightedChoice(array $weightPairs)
{
    if (!$weightPairs) {
        throw new \InvalidArgumentException('Weight pairs are empty');
    }

    $total = array_reduce($weightPairs, function($sum, $v) { return $sum + $v[1]; });
    $r = mt_rand(1, $total);

    reset($weightPairs);
    $acc = current($weightPairs)[1];
    while ($acc < $r && next($weightPairs)) {
        $acc += current($weightPairs)[1];
    }

    return current($weightPairs)[0];
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接