【问题标题】:Javascript Math.random() not *that* random…?Javascript Math.random() 不是 *that* random...?
【发布时间】:2013-07-06 15:54:14
【问题描述】:

我用 Javascript 编写了一个显示随机图片的小函数。返回图片编号的实际行如下所示:

num = Math.floor(Math.random() * RNDGALSIZE);

目前 RNDGALSIZE = 72。

但是,我觉得有些图片经常被点击,而有些图片很长时间没有出现,所以我写了一个循环来生成num的次数,并记录num的每个值出现了多少次.结果如下:

2, 1, 2, 1, 1, 3, 1, 2, 2, 2,    // num = 0 to 9
2, 1, 0, 1, 0, 2, 2, 1, 0, 1,    // num = 10 to 19
1, 2, 0, 1, 0, 0, 1, 1, 2, 1,    // num = 20 to 29
1, 0, 1, 2, 0, 2, 1, 0, 0, 0,    // num = 30 to 39
2, 2, 3, 3, 0, 1, 1, 1, 0, 3,    // num = 40 to 49
2, 1, 2, 1, 3, 2, 3, 2, 1, 1,    // num = 50 to 59
2, 1, 1, 0, 0, 2, 2, 1, 0, 1,    // num = 60 to 69
3, 1                             // num = 70 and 71

如您所见,没有任何值出现超过 3 次,而 16 个值根本没有出现。虽然有些值可能不会出现,但我认为 16 已经太多了。我的方法有什么问题吗?

更新:

稍后:

4, 4, 3, 6, 5, 5, 3, 3, 2, 2,
2, 2, 2, 2, 3, 5, 3, 1, 4, 2,
4, 3, 0, 1, 1, 0, 1, 4, 4, 4,
2, 0, 5, 3, 0, 4, 2, 0, 2, 1,
2, 3, 4, 3, 2, 4, 3, 2, 0, 5,
4, 4, 4, 2, 3, 4, 4, 4, 5, 1,
2, 2, 4, 2, 0, 3, 4, 2, 2, 1,
4, 1,

如您所见,虽然 3 被击中 6 次,但数组中仍有 7 个零:/

【问题讨论】:

  • 你确定RNDGALSIZE72 而不是4
  • 如果它在 4 的位置,我将如何让 array[71] 填充 1??
  • @重点是,如果RNDGALSIZE 是 72,你的随机数应该在 0 和 71 之间,现在有 0 和 3 之间的数字,然后是 0 和 4。
  • 发布SSCCE。如果问题和你想的一样根本,那应该很容易。
  • @rhavin 很抱歉误解了你的“数组”,现在我明白了。

标签: javascript random


【解决方案1】:

(这是为了详细说明lossleader的答案。)

p[n][k] 表示在n 试验之后,恰好出现k 不同值(共72 个)的概率。很难给出一个精确的封闭式表达式,但我们可以使用动态规划很容易地计算它:

var p = [];

p[0] = [];
p[0][0] = 1; // after 0 trials, 100% chance that 0 values have appeared
for(var k = 1; k <= 72; ++k) {
    p[0][k] = 0
}

for(var n = 1; n < 1000; ++n) {
    p[n] = [];
    p[n][0] = 0;
    for(var k = 1; k < n && k <= 72; ++k) {
        p[n][k] = p[n-1][k] * k / 72 + p[n-1][k-1] * (72-k+1) / 72;
    }
    if(n <= 72) {
        p[n][n] = p[n-1][n-1] * (72-n+1) / 72;
    }
    for(var k = n + 1; k <= 72; ++k) {
        p[n][k] = 0;
    }
}

鉴于此,我们可以计算在n 试验之后,我们仍然至少有z“零”(甚至没有出现过一次的值)的概率:

function probabilityAfterNTrialsOfAtLeastZZeroes(n, z) {
    var ret = 0;
    for(var k = 0; k <= 72 - z; ++k) {
        ret += p[n][k];
    }
    return ret;
}

所以经过 91 次试验,我们仍然有 16 个或更多“零”的概率是probabilityAfterNTrialsOfAtLeastZZeroes(91, 16),即 0.959,即 96%。 (实际上,您的 很少 有点令人惊讶:拥有 17 个或更多“零”的概率为 0.914,因此拥有 16 个或更少的概率仅为 8.6%。)

同样,经过 194 次试验后,我们仍有 7 个或更多“零”的概率为probabilityAfterNTrialsOfAtLeastZZeroes(194, 7),即 0.179,即 18%。因此,在 194 次试验之后,您通常会期望少于 7 个“零”,但如果您多次重复该实验,您会期望几乎每五次就有 7 个或更多“零”。

我们还可以计算n 试验后的预期“零”数:

function expectedZeroesAfterNTrials(n) {
    var ret = 0;
    for(var z = 0; z <= 72; ++z) {
        ret += z * p[n][72-z];
    }
    return ret;
}

经过 91 次试验,我们预计 expectedZeroesAfterNTrials(91)“零”,即 20.164,经过 194 次试验,我们预计 4.775“零”。

【讨论】:

    【解决方案2】:

    你的数字看起来很合理,

    Math.pow((71/72),194)*72 // odds of missing a number ^ tries * number of slots
    4.774719247726743
    

    由于我们无法得到分数,因此如果我们生活在“确定性随机”世界中,您应该有 5 个而不是 7 个“零”。

    相反,您应该只选择不替换,然后在 0 处重新启动池。

    编辑您可以从 jsconsole 运行的快速而肮脏的 100 个测试计数:

    function do_test() { x = []; for (var i = 0 ; i < 72 ; i++) x.push(0); for (var j = 0; j < 194; j++) x[Math.floor(Math.random()*72)]++; count = 0; for (var i = 0 ; i < 72 ; i++) if (!x[i]) count++; return count;}
    
    
    function do_tests(n) { y = []; for (var i = 0; i < n; i++) y.push(do_test()); return y;}
    
    z = do_tests(100)
    

    [5, 5, 3, 6, 4, 3, 2, 4, 3, 3, 4, 5, 2, 5, 5, 3, 7, 3, 3, 4, 6, 4, 7, 4, 3, 7, 6, 5, 8, 3, 3, 4, 5, 3, 3, 3, 8, 3, 4, 7, 8, 4, 6, 3, 4, 3, 4, 3, 2, 7, 6, 7, 7, 5, 3, 6, 1, 3, 6, 5, 3, 3, 5, 4, 4, 5, 3, 4, 6, 6, 4, 3, 3, 7, 4, 11, 6, 5, 9, 5, 3, 6, 7, 6, 9, 2, 1, 7, 3, 4, 4, 6, 6, 7, 7, 5, 2, 5, 9、6]

    j = 0; for (var i = 0; i < 100; i++) j+=z[i];
    

    473 // 所以 4.73 个零槽是这 100 次运行的平均值...

    【讨论】:

    • 我早些时候对此表示赞同——我对我的赞同表示满意,因为这给出了正确的总体思路——但我想指出,这实际上并不是预期的精确公式零的个数,因为遗漏一个值的概率并不独立于遗漏另一个值的概率。
    • 我一直在考虑这个问题,但我认为除非插槽很少,否则不会有太大影响。不过,可以肯定的是,我实际上运行了 100 次测试并得到 4.85 0 的平均数..
    • 当然,如果问题是“这是一个好的随机数生成器吗?”,那么使用它来计算期望值就是作弊。 :-P 但是,是的,你说得对,非独立性影响不大。我只是在考虑后计算了预期值(请参阅我的“答案”),并得到 4.774719247726673,它将您的值与小数点后 12 位相匹配。没想到这么近!
    • 是的,我假设 chrome 的优化器没有足够的洞察力来识别这种等效性和/或不会与我的大脑共享算法错误。 :)
    【解决方案3】:

    因此,解决方案是跟踪一个数字被命中的频率,如果随机生成器命中一个已经经常被命中的数字,则提供一个经常被忽略的数字……

    var minhit = Number.MAX_VALUE;
    var maxhit = 1;
    var index_min = 0;
    for (i=0; i<RNDGALSIZE; i++)
    {
      if (imgnum[i] < minhit)
      {
        minhit = imgnum[i];
      index_min = i;
      }
      else if (imgnum[i]>maxhit)
      {
        maxhit = imgnum[i];
      }
    }
    var num = Math.floor(Math.random() * RNDGALSIZE);
    if (imgnum[num] == maxhit)
    {
      num = index_min;
    }
    imgnum[num]++;
    

    结果:

    2, 2, 1, 2, 1, 1, 1, 1, 1, 1,
    1, 1, 1, 1, 1, 1, 1, 2, 1, 1,
    2, 1, 1, 1, 1, 1, 1, 1, 1, 1,
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
    1, 1, 1, 1, 1, 1, 1, 1, 2, 1,
    1, 1, 1, 1, 1, 1, 2, 1, 1, 1,
    1, 1, 
    

    【讨论】:

    • 更好的方法是用所有值 0..71 填充一个数组(可能每个值 2 到 3 次,所以你不会得到完全的唯一性),然后用简单的 @ 打乱该数组987654321@,然后遍历该数组以确定显示图片的顺序。
    猜你喜欢
    • 2012-03-21
    • 2018-06-03
    • 2011-07-31
    • 1970-01-01
    • 2023-04-11
    • 1970-01-01
    • 2021-06-07
    • 2017-02-19
    • 1970-01-01
    相关资源
    最近更新 更多