【问题标题】:How unique a 5-digit mt_rand() number is?5 位 mt_rand() 数字有多独特?
【发布时间】:2014-06-28 11:16:16
【问题描述】:

我只是想知道,如果您绘制 5 位数字,mt_rand() 数字有多独特? 在示例中,我尝试使用此函数获取 500 个随机数的列表,其中一些是重复的。

http://www.php.net/manual/en/function.mt-rand.php

<?php
header('Content-Type: text/plain');

$errors = array();
$uniques = array();
for($i = 0; $i < 500; ++$i)
{
    $random_code = mt_rand(10000, 99999);
    if(!in_array($random_code, $uniques))
    {
        $uniques[] = $random_code;
    }
    else
    {
        $errors[] = $random_code;
    }
}

/**
 * If you get any data in this array, it is not exactly unique
 * Run this script for few times and you may see some repeats
 */
print_r($errors);
?>

可能需要多少位数才能确保循环中抽取的前 500 个随机数是唯一的?

【问题讨论】:

  • 随机数不能保证是“唯一的”。
  • 查看Birthday Problem 了解问题背后的一些数学知识。
  • 随机!=唯一。如果您说出您使用这些数字的用途,您可能会得到更有用的答案。

标签: php


【解决方案1】:

生日悖论在这里发挥作用。如果您从 10000-99999 中选择一个随机数 500 次,则很有可能重复。

小数字的直观想法

如果您掷硬币两次,大约一半的时间会得到重复。如果你掷一个六面骰子两次,你会得到一个重复的 1/6 时间。如果您滚动 3 次,您将获得 4/9 (44%) 的重复次数。如果您滚动 4 次,您将获得至少一个重复的 13/18 (63.33%)。滚动第五次,结果为 49/54 (90.7%)。第六次滚动它,它是 98.5%。第七次滚动它,它是 100%。

如果您将 6 面骰子替换为 20 面骰子,则概率增长会慢一些,但确实会增长。 3 次滚动后,您有 14.5% 的机会重复。 6 次滚动后为 69.5%。 10 次滚动后,它是 96.7%,几乎可以确定。

数学

让我们定义一个函数f(num_rolls, num_sides) 将其推广到从有限的选择集中选择的任意随机数生成器的任意数量的滚动。我们将 f(num_rolls, num_sides) 定义为在 num_rolls 中没有重复的概率 num_sides-side die。

现在我们可以尝试为此建立一个递归定义。要获得num_rolls 唯一号码,您需要先滚动num_rolls-1 唯一号码,然后再滚动一个唯一号码,因为num_rolls-1 号码已被占用。因此

f(num_rolls, num_sides) = 
  f(num_rolls-1, num_sides) * (num_sides - (num_rolls - 1)) / num_sides

或者,

f(num_rolls + 1, num_side) = 
  f(num_rolls, num_sides) * (num_sides - num_rolls) / num_sides

此函数遵循逻辑衰减曲线,从 1 开始移动非常缓慢(由于 num_rolls 非常低,每一步的变化非常小),然后随着 num_rolls 的增长缓慢加速,然后最终随着函数的值越来越接近 0,逐渐减小。

我创建了一个 Google Docs 电子表格,其中内置了这个函数作为一个公式,让你在这里玩这个:https://docs.google.com/spreadsheets/d/1bNJ5RFBsXrBr_1BEXgWGein4iXtobsNjw9dCCVeI2_8

将此与您的具体问题联系起来

您已经掷出 90000 面骰子 500 次。上面的电子表格表明,假设一个完全随机的 mt_rand,您会在大约 75% 的时间里预计至少有一对重复。从数学上讲,您的代码执行的操作是从集合中选择 N 个元素替换。换句话说,你从 90000 件东西的袋子里挑出一个随机数,写下来,然后放回袋子里,然后再选另一个随机数,重复 500 次。听起来您希望所有数字都不同,换句话说,您希望从集合中选择 N 个元素不替换。有一些算法可以做到这一点。 Dave Chen 的 shuffle 然后 slice 的建议是一个相对简单的建议。来自 Qaribou 的 Josh 建议分别拒绝重复是另一种可能性。

【讨论】:

    【解决方案2】:

    您的问题涉及“生日问题”的变体,该问题询问一个班级是否有 N 个学生,至少有两个学生生日相同的概率是多少?见Wikipedia: The "Birthday Problem"

    您可以轻松修改此处显示的公式来回答您的问题。代替每个学生的生日有 365 个等概率的可能性,你有 90001 (=99999-10000+2) 可以在 10000 和 99999 之间生成的等概率整数。如果你生成 500 个这样的数字,至少两个数字将是相同的:

    P(500)= 1- 90001! / ( 90001^n (90001 - 500)!) = 0.75

    因此,您生成的 500 个数字中有 75% 的机会至少有两个相同,或者换句话说,只有 25% 的机会您将使用您的方法成功获得 500 个不同的数字目前正在使用。

    正如这里的其他人已经建议的那样,我建议检查算法中的重复数字,而不是盲目地生成随机数并希望任何一对数字之间都不匹配。

    【讨论】:

      【解决方案3】:

      如果数字真的是随机的,那么数字就有可能会重复。有多少个数字并不重要——添加更多数字会降低重复的可能性,但它总是有可能的。

      您最好检查是否存在冲突,然后循环直到没有冲突为止:

      $uniques = array();
      for($i = 0; $i < 500; $i++) {
          do {
              $code = mt_rand(10000, 99999);
          } while(in_array($code, $uniques));
          $uniques[] = $code
      }
      

      【讨论】:

      • 您的第一段绝对正确,但实施了此检查,它们不再是均匀分布的随机数。
      • @hek2mgl,我认为 OP 并不关心均匀分布,他只需要唯一的 500 个随机数,而 Josh 的代码解决了这个目的。
      • @ShankarDamodaran 想了想我也会说这个答案还可以。问题中没有要求纯粹的均匀分布,你是对的。
      • @JoshfromQaribou 这个呢:3v4l.org/eBpZt ?这不需要一次又一次地搜索数组。
      • @hek2mgl 问题是您可能不会每次都获得 500 个元素。 Clicky.
      【解决方案4】:

      为什么不使用范围、随机播放和切片?

      <?php
      
      $uniques = range(10000, 99999);
      shuffle($uniques);
      $uniques = array_slice($uniques, 0, 500);
      
      print_r($uniques);
      

      Output:

      Array
      (
          [0] => 91652
          [1] => 87559
          [2] => 68494
          [3] => 70561
          [4] => 16514
          [5] => 71605
          [6] => 96725
          [7] => 15908
          [8] => 14923
          [9] => 10752
          [10] => 13816
          *** truncated ***
      )
      

      这种方法成本较低,因为它不会每次都搜索数组以查看是否已添加项目。也就是说,它确实使这种方法不那么“随机”。应提供有关这些号码将在何处使用的更多信息。如果这是一个在线赌博网站,这将是最糟糕的!但是,如果将其用于返回星座网站的“幸运”数字,我认为就可以了。

      此外,可以扩展此方法,将 shuffle 方法更改为使用 mt_rand(原始方法仅使用 rand)。它也可能使用 openssl_random_pseudo_bytes,但这可能有点过头了。

      【讨论】:

      • @ShankarDamodaran 我没有在这里投票,但这个答案不好,这是错误的。它没有回答这个问题。乔希的回答是正确的。
      • @hek2mgl,我同意你的看法,这个答案没有提到任何关于 mt_rand() 函数 uniqueness 的内容。这只是解决问题的另一种方法。
      • @MarcAudet - 您需要在其他地方阅读有关如何确定随机性和适用性的信息(我不是专家),但让我怀疑的一件事是适合;每个条目必须在每次洗牌中仅安装一次(并且必须表示),这意味着每个数字都被表示。与 OP 的愿望相反,这减少了“随机性”,因为重复项是真正随机的,其中不代表组中的适应度。这可能等同于另一个答案(我没有推测的数学技能),但我的猜测是更糟。
      • 这种方法是我觉得合适的方法,除非问题提供了进一步的信息(例如这些数字将在哪里使用,例如在线赌博、随机选择帖子等)。连续搜索一个大小达到 500 个元素的数组是昂贵的,因此它在很大程度上取决于这些数字的随机性。如果您确实要求它们更加随机,我不明白为什么不能完成另一个 shuffle 实现。
      • 我在选择您的方法时看到的重要因素是您的 n = 所需值的数量。如果你是 n=3,那么这是一种非常过分的方法,我认为性能 + 随机性很重要,因为 mt_rand() 被选择而不是 rand()。如果您有 n=89999,那么用于挑选新数字的 do{}while() 解决方案将以蜗牛的速度运行。 n=500 似乎太低,以至于 shuffle(range()) 没有意义。
      猜你喜欢
      • 2016-02-02
      • 2011-05-21
      • 2014-01-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多