什么是随机性?
计算机与人类大脑的共同点是它们非常不擅长随机化。
如果没有一些外部输入(掷骰子、测量放射性衰变、收听未调谐的收音机,staring at lava lamps),您实际上无法写下一组产生随机性的步骤。您所能做的就是制作一个看起来随机的实际可预测的序列。不幸的是,很容易欺骗人脑,让其认为某些东西是随机的,或者发现没有的模式,因此在设计这种“伪随机性”时必须非常小心。
人类大脑如何感知随机性的一个相关示例是,如果一个房间里只有 23 个人,那么其中两个人生日相同的可能性为 50%。这太令人惊讶了,它通常被称为“birthday paradox”,尽管它在数学上非常简单。你的文字游戏中的字母簇非常相似。
发生了什么变化?
长期以来,PHP 有两种不同的“伪随机数生成器”:
- 一种依赖于系统的算法,以
rand() 的形式向用户公开,存在很多问题。
- 一种叫做“Mersenne Twister”的东西,以
mt_rand() 的形式向用户公开,总体来说在所有方面都更好。
在 PHP 7.1 中,a series of changes were made 基本消除了rand() 版本,并在各处使用 Mersenne Twister 算法。这包括shuffle。
算法的其余部分都没有改变:总是打算将元素按随机顺序排列,而不管它们的起始位置如何,所以你通过聚集起始位置来影响它的能力是错误,不是功能。
不同版本中随机播放的随机性如何?
我写了一个quick script 来测试shuffle 是否有任何偏见:
- 生成一个包含 100 个项目的数组,从 0 到 99
- 随机播放
- 标记每个元素的新位置
- 重复很多次
结果是一个 100 x 100 的网格,显示例如元素 0 在位置 42 结束的频率。
如果洗牌是真正随机的,我们应该期望,当我们重复洗牌足够多次时,每个元素都会以同样的频率出现在每个位置。换句话说,我们的网格对于每个组合都应该具有相同的计数。
我在 PHP 7.0(当 shuffle() 使用旧的 rand() 实现时)和 7.1(当它使用 mt_rand() 实现时)运行相同的代码并可视化结果(脚本还包括 in the gist)。图像的列表示数组中的 100 个元素,行表示它们最终可能出现的不同位置。蓝色像素表示发生频率低于预期的组合,红色像素表示发生频率高于预期的组合。
对于 PHP 7.1,图像看起来大部分是黑色的,因为所有组合中的数字非常接近完全相等:
然而,对于 PHP 7.0,有一些非常明显的“热”和“冷”区域!
因此,在旧算法中,某些交换发生的可能性要小得多。这些偏差可能恰好与您放置物品的顺序相吻合,从而降低了某些字母在游戏中特定时间出现的可能性。
如果你想调整你的随机播放怎么办?
新算法应该更准确地反映真实游戏中会发生的情况 - 所有字母在游戏中的任何时候出现的机会均等。
但也许您发现“操纵”订单更有趣,这样您就不会连续多次收到相同的字母,或者您可能会混合使用辅音和元音。
您可以通过重新实现随机播放算法,然后调整某些事情发生的概率来实现。
算法的核心是这样的(基本上7.1中改变的是RAND_RANGE的定义):
while (--n_left) {
RAND_RANGE(rnd_idx, 0, n_left, PHP_RAND_MAX);
if (rnd_idx != n_left) {
temp = hash->arData[n_left];
hash->arData[n_left] = hash->arData[rnd_idx];
hash->arData[rnd_idx] = temp;
}
}
正如您所期望的那样,这会一个接一个地随机选择项目(但从最后一个到第一个,因为它恰好更容易),但是为了节省每次“删除”一个项目时重新分配数组,它使用了一个巧妙的技巧:它交换数组的当前元素与数组中较早的随机元素。
在 PHP 代码中,它看起来像这样(未经测试!):
$n_left = count($array);
while (--$n_left) {
$rnd_idx = mt_rand(0, $n_left);
// The if statement is just skipping the swap if our random choice is for it to stay where it is
if ($rnd_idx != $n_left) {
$temp = $array[$n_left];
$array[$n_left] = $array[$rnd_idx];
$array[$rnd_idx] = $temp;
}
}
要为此添加偏见,您只需更改选择$rnd_idx 的方式。例如,您可以说如果选择的字母 ($array[$rnd_idx]) 与之前选择的字母相同,请再次选择。你可以有一个完整的函数来决定它有多“好”,并以这种方式引入偏见。
由于“有趣”是主观的,无法通过数学定义,因此您只需测试一堆东西,看看您喜欢哪个结果。