生成 BigInt 值的随机样本

Generating a random sample of BigInt values

提问人:Domino 提问时间:5/5/2020 更新时间:5/6/2020 访问量:513

问:

对于用 JavaScript 实现的游戏,我需要生成一个唯一数字的随机列表,其范围可能大于 。这带来了三个重要挑战:n[0, N)NNumber.MAX_SAFE_INTEGER

  • 内存成本不能是 O(N),因为我们没有足够的 RAM。这意味着我们不能使用修改后的 Fisher-Yates 随机算法从元素数组中获取随机值。(JS 数组仅限于 32 位索引,但在此之前我们早就耗尽了 RAM。nN
  • 执行时间应尽可能接近 O(n) 而不是 O(N)。我预计 n 会相对较小(< 100)。
  • 大多数数学运算使用双精度浮点值,不适用于这么大的数字。

我在 Kim-Hung Li 的储层采样算法 L 中找到了解决前两个挑战的解决方案。总而言之,这个算法背后的想法是,我们洗牌第一个数字以形成一个初始水库,然后我们用从范围的其余部分获取的随机数逐步覆盖其中的一些数字。我们使用几何级数来确定每次迭代要跳过多少个数字。n

假设 sampleSize 是一个数字,populationSize 是一个 BigInt,代码如下:

// Creates an array initialized with [0n, 1n, ... BigInt(count-1)].
function bigRange (count) {
    const array = Array(count);
    for (let i = 0; i < count; i += 1) {
        array[i] = BigInt(i);
    }
    return array;
}

function bigSample (sampleSize, populationSize) {
    const reservoir = shuffle(bigRange(sampleSize));
    let record = BigInt(sampleSize);
    let weight = exp(log(random()) / sampleSize);

    while (true) {
        const skipCount = floor(log(random()) / log(1 - weight));
        record += BigInt(skipCount);

        if (record >= populationSize) {
            return reservoir;
        }

        reservoir[floor(random() * sampleSize)] = record;
        record += BigInt(1);
        weight *= exp(log(random()) / sampleSize);
    }
}

但是,此代码不适用于任意大的数字,因为浮点数的精度是有限的。随着 populationSize 的增长,浮点数的非有效位最终被使用,我们不再具有均匀分布,然后我们进入不安全的整数区域,最后有可能成为 .知道了这一点,我就想知道......skipCountInfinity

  • 在什么时候,此功能变得不可靠?
  • 我有没有办法改进这个算法来弥补 JavaScript 的局限性?
  • 有没有更好的选择?

请注意,我对概率的了解是有限的,我花了一周时间挖掘我几乎不理解的科学论文后编写了这段代码。我确实有另一种方法,但它的效率要低得多,并且需要将随机数生成与拼图生成逻辑混合在一起。

JavaScript 算法 随机 浮动精度 bigint

评论


答:

2赞 Peter O. 5/5/2020 #1

如果相对较小,另一种方法是将已生成的数字存储在 JavaScript Map 或普通 JavaScript 对象中,其中键是数字(但请注意,Maps 和普通对象都会将这些键转换为字符串,因此在这种情况下,键应该与值相同)。如果远小于存储每个新随机数的时间,则接近恒定时间复杂度,并且所有随机数在第一次尝试时都不同的概率接近 1。nnNnN


  • 我写了一节,展示了根据大小和数量对物品进行抽样的各种方法nNnN
  • 但是,如果你试图从大量可能的值(例如,100 位数字)中生成唯一的随机数,并且这些数字是为了以某种方式识别某些东西,那么有几件事需要记住,我在“唯一随机标识符”中详细介绍了这一点。nN

评论

0赞 Domino 5/5/2020
我只是仔细检查了一下,Map 和 Set 实际上允许任何值作为键,甚至是 BigInt,我忘记了,因为 WeakMap 只允许对象。我可以调用伪随机 BigInt 生成器 n 次,并在我生成我已经选择的数字时重试,只要很小,这确实是一个选项。我可以验证这个比率并在可行时切换算法。我会调查的。n/N
0赞 Peter O. 5/5/2020
请参阅对我的问题的编辑,其中讨论了根据大小和数量从项目中抽样的各种方法,以及使用唯一随机数要记住的事项。nNnN
0赞 Domino 5/5/2020
哇,这是一个非常有用的参考,谢谢。我将尝试使用 Crypto API 实现 RNDINT,因为 ECMAScript 标准中的定义过于松散。与此同时,我将把这个问题留待解决,但我最终可能会将其标记为公认的解决方案,并在我自己的答案中发布我的最终 JS 代码。Math.random
1赞 Roman Svistunov 5/5/2020 #2

您可以一点一点地生成值并将它们添加到尝试中。当您生成的不是第一个数字时,您可以使用尝试分支中的可用空间计数来确定进入每个分支的概率。如果浮点运算是完美的,这将从均匀分布中给出唯一的数字,但我想这些在尝试中高水平概率的不一致不会产生显着差异(因此,这需要进一步研究)。请注意,如果您生成了一个大于 N 的数字,则只需重新开始生成过程。如果您不制作不必要的比特,则再生的概率将小于 50%,并且额外几代的期望值为 1。

评论

0赞 Domino 5/5/2020
本周早些时候我确实读到了关于尝试的文章,这实际上是我最初的方法,但我找不到太多关于它们用于随机生成的信息。如果您有任何参考资料,我可以咨询,我很感兴趣。我没有在问题中提到这一点,因为我也对通用答案感兴趣,但我实际上知道 where 和 是小整数,这意味着我可以尝试节点具有确切子项的深度,从而完全避免再生问题。N=a**babba