【问题标题】:Select row at random based on the "weight" of that row根据该行的“权重”随机选择行
【发布时间】:2018-07-27 09:21:41
【问题描述】:

我有一张这样的桌子:

ID chance
1 1
2 2
3 4
4 1

现在我需要从这个表中选择一个 rand()

SELECT * FROM table
ORDER BY RAND()
LIMIT 1

但 ID #2 与 ID #1 和 4 相比,被选中的机会是 ID #1 和 4 的两倍。与 ID #1 和 4 相比,ID #3 的被选中机会是 ID #3 的四倍。

有点类似于基于机会的彩票。

【问题讨论】:

  • 对不起我的纯英语是的,没关系
  • 能否请您edit您的问题并添加示例您如何使用'rand()'

标签: php mysql sql random


【解决方案1】:

如果您需要明确的 MySQL 解决方案,您可以使用:

SELECT id FROM `table` ORDER BY -LOG(1-RAND())/chance LIMIT 1

这里是关于从指数分布中选择一个随机数 http://www.tushar-mehta.com/publish_train/xl_vba_cases/0806%20generate%20random%20numbers.shtml

简单代码“仅供测试”

$sql = "SELECT id FROM `table` ORDER BY -LOG(1-RAND())/chance LIMIT 1";
$Res=array();
for ($i=0;$i<10000;$i++) {
    $result = mysqli_query($db,$sql);
    $row=mysqli_fetch_array($result, MYSQLI_ASSOC);
    if (isset($row['id'])) {
       echo "$i. => ".($row['id'])."\n";
       if (!isset($Res[$row['id']])) $Res[$row['id']]=0;
       $Res[$row['id']]++;
    } else {
        echo ' error.432 ';exit;
    }
}

print_r($Res);

您会看到“2”比“4”或“1”多出两倍。而“3”的频率是“2”的两倍

【讨论】:

    【解决方案2】:

    这就是彩票在某些游戏中的运作方式。给定与您的示例类似的表格(例如,我们还有chance 列,表明获得此特定奖励的基于价值的可能性),算法是:

    1. 计算彩票总价值(在您的示例中为1 + 2 + 4 + 1 = 8)。
    2. 1..max(在当前示例中max8)范围内生成一个值。
    3. 遍历奖励列表中的所有项目以找到所有先前机会的总和大于生成数但小于或等于的项目

    说,我们生成了号码5。我们的比较步骤是:

    1. 0 &lt; 5 &lt;= (0) + 1 是假的,所以 ID1 不是我们得到的。左边是 0,因为我们从 0 开始计算。
    2. 1 &lt; 5 &lt;= (1) + 2 是假的,所以 ID2 不是我们得到的。
    3. 1 + 2 &lt; 5 &lt;= (1 + 2) + 4 为真,所以我们得到了 ID3。

    JavaScript 中的示例:

    var rewards = [
      { id: 1, chance: 1 },
      { id: 2, chance: 2 },
      { id: 3, chance: 4 },
      { id: 4, chance: 1 }
    ];
    
    function getRandomInt(min, max) {
      return Math.floor(Math.random() * (max - min + 1)) + min;
    }
    
    function generate() {
      var sum = 0;
      var next_sum = 0;
      var random = getRandomInt(1, rewards.reduce(function(pv, cv) {
        return pv + cv.chance;
      }, 0));
    
      for (var i = 0; i < rewards.length; i++) {
        next_sum = sum + rewards[i].chance;
        if ((random > sum) && (random <= next_sum)) {
          return rewards[i].id;
        }
        sum += rewards[i].chance;
      }
    }
    
    var winnerCounts = {}, i, winner;
    for (i = 0; i < 8000; i++) {
      winner = generate();
      winnerCounts[winner] = (winnerCounts[winner] || 0) + 1;
    }
    console.log("Number of times each id was selected after %d itrations", i);
    console.log(winnerCounts);

    【讨论】:

    • @x-rw 该算法与机器学习无关,至少不打算
    【解决方案3】:

    这里是 SQL Fiddle,带有仅 MySQL 的解决方案

    select * from (
      select id, @running_total as previous_total, @running_total := @running_total + chance AS running_total, until.rand
      from (
        select round(rand() * init.max) as rand from (
          select sum(chance) - 1 as max from demo
        ) as init
      ) as until,
      demo,
      ( select @running_total := 0.00 ) as vars
    ) as results
    where results.rand >= results.previous_total and results.rand < results.running_total
    

    算法如下:

    1. 找出所有机会的总和并将其存储在max
    2. 在区间[0, max)中生成一个随机数
    3. 对于每一行,计算到目前为止遇到的机会的 previous_total (initially 0)current_total
    4. 只保留生成数在区间[previous_total, current_total)内的行

    因为我们有机会在区间[0, sum_of_all_chances) 中选择每个数字,所以我们可以在这个区间内为每个条目分配尽可能多的数字,因为它有机会被选择,确保均匀分布。

    @running_total 只是一个 MySQL 变量,我使用 ( select @running_total := 0.00 ) as vars 只是为了给它一个初始值。 另外,我使用( select round(rand() * init.max) as rand from ( select sum(chance) - 1 as max from demo ) as init ) as until 来总结机会并存储MySQL 的rand 函数生成的随机数。希望这使代码易于理解。

    【讨论】:

      猜你喜欢
      • 2013-05-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-02-19
      • 2021-10-28
      • 2017-02-15
      相关资源
      最近更新 更多