【问题标题】:PHP - Improve random number sequence generationPHP - 改进随机数序列生成
【发布时间】:2015-10-27 20:12:58
【问题描述】:

我想这是我关于 SO 的第一个问题。

我目前正在开发一个网站,我必须为彩票生成 1 到 29 之间的 6 个数字(每个数字最多一个)。因为它们可以按任何顺序排列,所以我只是在之后对它们进行排序。

如果我没记错的话,应该是(29*28*27*26*25*24) / 6! = 475020有不同的可能组合。

我尝试了不同的方法来生成序列,或者使用 mt_rand 或 random_int(来自 random_compat)但是当我用类似 10k 的迭代测试它时,我总是得到大约 100 个重复,即使它们是 465k 组合仍然可用。

这是我一直在尝试的代码示例:

// Using an array and mt_rand (or random_int, giving same results)
// Also tried shuffling the array instead of simply reindexing it, not better

$values = range(1, 29);

while(count($values) > 6) {
    unset($values[mt_rand(0, count($values) - 1)]);
    $values = array_values($values);
}



// Creating the array from random numbers (same results using random_int)

$values = array();
while (count($values) < 6) {
    $r = mt_rand(1, 29);
    if (in_array($r, $values)) {
        continue;
    } else {
        $values[] = $r;
    }
}

好吧...我的问题是:

  • 有没有办法改进我目前正在做的事情?
  • 会不会是这样,我必须处理它?
  • 我只是做错了吗?

谢谢!

林恩。

PS : 看了很多问题,但没有找到任何满足我需求的东西,如果我看起来不够好,请见谅!

只是为了澄清一些事情:使用 random_int(它使用 /dev/urandom 或 openssl_random_pseudo_bytes)并没有改善任何东西,我认为这会改善。如果可能的话,我不想使用任何外部 API(如 random.org)。

【问题讨论】:

  • $numbers = range(1,29); shuffle($range); $draw = array_slice($range, 0, 6); echo implode(',' $draw);.... 在任何给定的平局中,您永远不会得到任何重复
  • @MarkBaker implode方法的参数顺序错误。
  • @MarkBaker 我没有在特定抽奖中得到重复,我得到重复抽奖。抱歉,如果不够清楚:)
  • @MeneerVenus 不,它们的顺序正确,但缺少逗号。接受其他参数顺序但不推荐:php.net/manual/en/function.implode.php
  • @Lynesth 哦,我还以为是致命错误,结果发现缺少逗号,而 $numbers 变量应该被称为 $range

标签: php random numbers


【解决方案1】:

使用 random_int(使用 /dev/urandom 或 openssl_random_pseudo_bytes)并没有改善任何东西,我认为这会。

当然可以,只是您无法通过视觉识别。 mt_rand()rand() 只允许大约 232 个可能的种子和 232 个可能的输出,最重要的是,它们具有确定性序列:@ 987654321@.

您的操作系统的 CSPRNG 没有任何此类限制。知道少数random_int() 输出(在 PHP 中,在 32 位系统上限制为 232 个可能的值,在 64 位系统上限制为 264)不会给出关于未来产出的任何信息。

我目前正在开发一个网站,我必须为彩票生成 1 到 29 之间的 6 个数字(每个数字最多一个)。因为它们可以按任何顺序排列,所以我只是在之后对它们进行排序。

好的,这是个好主意。你肯定想要一个 CSPRNG 在这里。

当我用 10k 次迭代测试它时,我总是得到大约 100 个重复项,即使它们仍然有 465k 组合可用。

正如其他人所指出的,这就是 birthday problem/paradox 在起作用。

如果您需要解决方案,请尝试以下方法:

function random_unique_select($num, array $possible_values)
{
    $sizeof = count($possible_values);
    if ($num > $sizeof) {
        throw new InvalidArgumentException('$num is too large');
    }
    $selected = [];
    for ($i = 0; $i < $num; ++$i) {
        // Grab a random int [0, ... N - 1]
        $r = random_int(0, $sizeof - 1);
        // Copy the selected value into $selected
        $selected[] = $possible_values[$r];
        // Delete it from the range of possible values
        unset($possible_values[$r]);
        // N has grown smaller by 1
        --$sizeof;
        // Reset keys; we want this to be zero-indexed.
        $possible_values = array_values($possible_values);
    }
    return $selected;
}

$lottery = random_unique_select(6, range(1,29));

演示:

【讨论】:

  • 您提供的代码示例没有太大变化。我还尝试使用 random.org 生成 10k 序列,我得到的重复次数比使用我的方法还要多,所以我想没关系。选择您的答案,因为这是我更喜欢的答案,但感谢大家的回答!
【解决方案2】:

阅读Birthday Paradox

根据我的计算 (bc calculator),在 812 个序列的情况下,29 个项目中有 6 个重复组合的概率为 50% 或更高。

define p(n, k) { return (n-k)/n; }
n=475020
m=1; for (k=0; k<811; k++) m *= p(n, k); m
.500649663424
m=1; for (k=0; k<812; k++) m *= p(n, k); m
.499794905988

【讨论】:

  • 得到 10,000 个序列的重复的概率约为 99.99999999999999999999999999999999999999999999990751%
  • 所以我想这意味着我已经做得足够好了?为什么我看不出 mt_rand 和其他一些“更好”的方法(例如 /dev/urandom)之间有什么区别?
  • 有没有办法计算“在投射 X 序列时我应该期望多少重复?” ?导致每次我没有得到一个重复的概率都会增加,而当我得到一个时就保持不变?还是我想太多了,我应该继续我所拥有的?
  • 如果要保证不重复,请将所有 (475,020) 值放入一个数组中,打乱数组并选择第一个 N (10,000)。
【解决方案3】:

rand() 和 mt_rand() 依靠纯数学来产生伪随机数。

要获得真正的随机数,您可以使用http://www.random.org 的网络服务

【讨论】:

  • 这是最简单和最随机的解决方案,但您依赖于 API。计算机生成的数字不能是随机的,只能是伪随机的。
  • 我知道random.org,但正如@user3469305 刚才所说,我不想依赖外部API 来做。想要我不太明白的是,我使用 mt_rand 或 random_int 得到了相同的结果(如果你看一下实现,它使用 /dev/urandom,它在服务器上可用)。
【解决方案4】:

为了提高“随机性”,您可以尝试加密库,例如phpseclib.

他们的数学库 here 中有一个 random() 函数。

编辑:计算机生成的数字不能随机。使用密码库可以获得的最佳伪随机结果,最简单、最随机的解决方案是@Matthias Leuffen 之一。

【讨论】:

    【解决方案5】:

    如果安装了正确的扩展,您可以使用openssl_random_pseudo_bytes()

    例子:

    function strong_random() {
        return hexdec(bin2hex(openssl_random_pseudo_bytes(20)));
    }
    

    注意:由于openssl_random_pseudo_bytes()的实现,这个函数会非常慢

    当然,又快又脏,可以使用添加最大长度参数。

    【讨论】:

    • 你的函数不正确,你应该传递一个变量,而不是一个布尔值作为第二个参数。但正如我已经说过的,使用 /dev/urandom 或 openssl_random_pseudo_bytes 并没有改善任何东西。
    • 是一个快速而肮脏的例子,我现在已经修复了。
    • "注意:由于openssl_random_pseudo_bytes()的实现,这个函数会很慢。"呵呵。将openssl_random_pseudo_bytes()转成整数其实比mt_rand()快。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-01-20
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多