【问题标题】:Generate 3 distinct random integers via a uniformly distributed RNG通过均匀分布的 RNG 生成 3 个不同的随机整数
【发布时间】:2020-10-14 17:21:14
【问题描述】:

假设我有一个randint(A, B) 函数,它在[A, B] (包括)范围内生成一个均匀分布的随机整数。例如,1 <= randint(1, 10) <= 10。如何在一定范围内高效地随机生成 3 个不同的整数?

这应该通过对 randint(A, B) 的 3 次调用来完成。我不在乎我得到的三个数字的哪个排列;它可以是随机的或具有定义的顺序。

我不只是试图找到任何算法来做到这一点,而是(在合理范围内)使用最少额外内存的最快算法。

def rand3(low, high):
    assert high - low + 1 >= 3
    # Magic!
    return n1, n2, n3 # each number has low <= n <= high, each number is distinct

最简单的方法是生成一个数字列表[low, low + 1, ..., high],然后执行 Fisher-Yates 洗牌以选择 3 个元素,尽管成本很高。没有必要全面评估 Fischer-Yates,因为我们可以滚动三个指数。但是,我绝对不想在内存中创建这个数组。


对于 2 个整数,将范围内的第二个整数滚动小于一个就足够了,如果它大于或等于第一个数字,则向上调整其值:

def rand2(low, high):
    i1 = randint(low, high)
    i2 = randint(low, high - 1)
    n1 = i1
    n2 = i2 + (i2 >= n1)
    return n1, n2

但是,将其扩展到三个整数并不简单。我相信应该可以做这样的事情:

# I _think_ this works?
def rand3(low, high):
    i1 = randint(low, high)
    i2 = randint(low, high - 1)
    i3 = randint(low, high - 2)

    n1 = i1
    n2 = i2 + (i2 >= n1)
    if n1 > n2: n1, n2 = n2, n1
    n3 = i3 + (i3 >= n1)
    n3 = n3 + (n3 >= n2)

    return n1, n2, n3

但是,我觉得必须有比这更简单的东西。 3 元素问题真的要复杂得多,以至于我们需要对 2 个整数进行排序并且第二个条件 (n3 &gt;= n2) 必须依赖于 n3 的中间值吗?我期待更接近 2 元素问题的东西。

【问题讨论】:

  • 也称为“从一个区间中抽取 3 个随机整数而不进行替换”

标签: algorithm random


【解决方案1】:

我不知道这些变化是否让你更快乐,但这个rand3至少少了一个比较。

这个想法是模拟Fisher--Yates的几个步骤。在rand3 中,我们从索引i3 向后工作以查找值。第一个if 撤消第二次交换,第二个if 撤消第一次交换。

import collections
from random import randint


def rand2(low, high):
    i1 = randint(low, high)
    i2 = randint(low, high - 1)
    if i2 == i1:
        i2 = high
    return i1, i2


def rand3(low, high):
    i1 = randint(low, high)
    i2 = randint(low, high - 1)
    i3 = randint(low, high - 2)
    if i3 == i2:
        i3 = high - 1
    if i3 == i1:
        i3 = high
    if i2 == i1:
        i2 = high
    return i1, i2, i3


print(collections.Counter(rand3(1, 4) for i in range(1000000)))

【讨论】:

  • 从我在问题中的最后一个想法开始,虽然我想要比它更好的东西,但问题是我什至不知道它是否有效。我认为它有效,但我真的不知道。因此,我并不是在试图找到一种“让我快乐”的变体,而是我试图找到一种适用于 O(1) 空间和 3 次调用 randint 的变体。所以是的,你的解决方案确实适用于我想要找到的东西
  • @Justin 好的,已修复。这个想法是模拟Fisher--Yates的三个步骤。
  • 这是有道理的。实际上,我也尝试根据 Fisher-Yates 对我的想法进行建模,但我认为在这里通过您的算法进行推理要容易得多。
  • 我做了一些简单的基准测试,这个版本是我测试过的最快的。 C++ 编译器将分支优化为条件移动。我认为尝试在此基础上进行改进并不重要,因为randint(...) 实际上是该函数花费所有时间在做的事情。即使使用非常快的随机数生成器,随机质量低于标准(仅针对速度进行优化),对randint() 的调用也花费了超过 5/6 的时间。
【解决方案2】:

最简单但可能不是最高效的方法是在随机数不符合条件时继续重新选择它们。检查重复项很简单,只需创建一个set,重复项就会被丢弃。

def rand3(low, high):
    assert high - low + 1 >= 3
    s = set()
    while len(s) != 3:
        s = set([randint(low, high), randint(low, high), randint(low, high)])
    return tuple(set)

【讨论】:

  • 我绝对不想这样做。我想通过对randint 的三个调用来做到这一点。问题中的代码虽然是 python,但并不是说我想用 Python 完成这个算法,而是展示一个我想要的例子。我正在尝试为任何编程语言找到一个好的算法
猜你喜欢
  • 1970-01-01
  • 2017-07-16
  • 1970-01-01
  • 2014-02-04
  • 1970-01-01
  • 2013-12-09
  • 2012-09-18
  • 1970-01-01
  • 2013-07-22
相关资源
最近更新 更多