5位mt_rand()数的唯一性如何?

时间:2014-05-10 20:55:41

标签: php

我只是想知道,如果你绘制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个随机数是唯一的?

4 个答案:

答案 0 :(得分:7)

如果数字是真正随机的,那么数字将被重复的概率。没有多少数字并不重要 - 添加更多数字会使得重复的可能性降低,但它总是有可能。但

你最好先检查一下是否存在冲突,然后循环直到不是这样:

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

答案 1 :(得分:5)

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

<?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,但这可能有点过分。

答案 2 :(得分:3)

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

小数字直观的想法

如果你翻了两次硬币,你会在一半时间内获得一份副本。如果您将六面模具滚动两次,那么您将获得1/6的重复时间。如果你滚动3次,你会得到重复的4/9(44%)时间。如果你滚动4次,你将获得至少一个重复的13/18(63.33%)。第五次滚动它是49/54(90.7%)。第六次滚动它是98.5%。第七次滚动它是100%。

如果用20面模具取代六面模具,概率会慢一些,但增长速度会慢一些。在3次掷骰后,您有14.5%的重复几率。 6轮后,它的比例为69.5%。经过10次滚动后,它几乎达到了96.7%。

数学

让我们定义一个函数f(num_rolls, num_sides),将其概括为任何随机数生成器的任意数量的卷,这些随机数生成器选择有限的一组选择。我们将f(num_rolls, num_sides)定义为在num_rolls侧死亡的num_sides中没有重复的概率。

现在我们可以尝试为此构建递归定义。要获得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文档电子表格,该电子表格内置了此功能,可让您在此处使用此功能:https://docs.google.com/spreadsheets/d/1bNJ5RFBsXrBr_1BEXgWGein4iXtobsNjw9dCCVeI2_8

将此问题与特定问题联系起来

您已经生成500倍的90000双面模具。上面的电子表格表明,假设一个完全随机的mt_rand,大约75%的时间内至少会有一个重复对。在数学上,您的代码执行的操作是从具有替换 的集合中选择N个元素。换句话说,你从90000的东西中挑出一个随机数字,写下来,然后把它放回袋子里,然后挑选另一个随机数,重复500次。听起来你希望所有的数字都是截然不同的,换句话说你想要从一组中选择N个元素,而无需替换 。有一些算法可以做到这一点。 Dave Chen提出的洗牌和切片的建议是相对简单的。来自Qaribou的Josh建议分别拒绝重复是另一种可能性。

答案 3 :(得分:1)

您的问题涉及&#34;生日问题&#34;询问班上是否有N名学生,至少有两名学生生日相同的概率是多少?请参阅Wikipedia: The "Birthday Problem"

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

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

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

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