将随机范围从1–5扩展到1–7


692

给定一个产生1到5范围内的随机整数的函数,编写一个产生1到7范围内的随机整数的函数。

  1. 什么是简单的解决方案?
  2. 什么是减少内存使用或在较慢的CPU上运行的有效解决方案?

事实证明,这是一个出乎意料的有趣问题,我仍然认为该如何做到:1)在固定时间内完成; 2)不破坏均匀分布(如果有)
eugensk

从骰子中选择5名玩家中的一位时,我们遇到了类似的问题。我们轮流掷骰子,选择了获得最高分的人。达到了均匀性,但没有达到时间常数:)
eugensk

如果我发布一个回答说问题不强制您必须使用给定的函数,而只写一个随机返回1-7的函数,我会感到沮丧吗?
蓝色医生

7 * rand5() / 5
kiwixz 2015年

@kiwixz,将产生“ 1到7之间”,但您不会得到3或6:{1:19.96,2:20.02,4:20.01,5:19.99,7:20.02}手动测试的大致百分比。7 * .2、7 * .4、7 * .6、7 * .8、7 * 1。
pythonlarry '16

Answers:


572

这等效于Adam Rosenfield的解决方案,但对某些读者而言可能更清楚。假定rand5()是一个函数,该函数返回1到5(含)范围内的统计随机整数。

int rand7()
{
    int vals[5][5] = {
        { 1, 2, 3, 4, 5 },
        { 6, 7, 1, 2, 3 },
        { 4, 5, 6, 7, 1 },
        { 2, 3, 4, 5, 6 },
        { 7, 0, 0, 0, 0 }
    };

    int result = 0;
    while (result == 0)
    {
        int i = rand5();
        int j = rand5();
        result = vals[i-1][j-1];
    }
    return result;
}

它是如何工作的?像这样想:想象一下将这种二维阵列打印在纸上,将其固定在飞镖板上,然后随机向其投掷飞镖。如果您命中非零值,则它是1到7之间的统计随机值,因为有相等数量的非零值可供选择。如果击中零,则继续掷飞镖,直到击中非零为止。这就是代码的作用:i和j索引在飞镖板上随机选择一个位置,如果结果不佳,我们将继续扔飞镖。

就像亚当说的那样,这在最坏的情况下可以永远持续下去,但从统计上讲,最坏的情况永远不会发生。:)


5
我了解此解决方案背后的逻辑,但无法理解它如何导致统一的概率?有人可以解释一下数学吗?
user1071840 2012年

6
@ user1071840-如果rand5是统一的,则vals网格中的每个单元都有被拾取的相等概率。网格在间隔[1,7]中正好包含每个整数的三个副本,外加四个零。因此,“原始”结果流趋向于均匀混合[1,7]值,加上一些零,其出现的时间比任何单个允许值更频繁。但这并不重要,因为去除了零,只剩下[1,7]值的均匀混合。
Daniel Earwicker 2012年

3
实现此问题的捷径是:如果只调用rand5()一次,则只有5种可能的结果。如果不增加更多随机性,显然没有办法将其转换为5种以上的可能结果。
Daniel Earwicker 2012年

1
较长的版本:rand5()只能具有值(1、2、3、4、5)。因此rand5()* 5只能具有值(5、10、15、20、25),该值与完整范围(1 ... 25)不同。如果是的话,减去4便得出(-3 ... 21),但是在这种情况下,它变成了(1、6、11、16、16、21),所以端点是正确的,但是有四个大洞:( 2..5),(7..10),(12..15),(17..21)。最后,您执行mod 7并添加1,得到(2,7,5,5,3,1)。因此,4和6都不会发生。但是(请参见上面的快捷方式),我们知道在结果范围内始终只能有5个数字,因此必须有两个间隔。
Daniel Earwicker 2012年

1
啊,因为我们只有rand5(),没有rand2():-)
gzak 2014年

352

没有(完全正确)的解决方案可以在恒定的时间内运行,因为1/7是以5为底的无限小数。一个简单的解决方案是使用拒绝采样,例如:


int i;
do
{
  i = 5 * (rand5() - 1) + rand5();  // i is now uniformly random between 1 and 25
} while(i > 21);
// i is now uniformly random between 1 and 21
return i % 7 + 1;  // result is now uniformly random between 1 and 7

预期的运行时为循环的25/21 = 1.19迭代,但永远循环的可能性极小。


7
如果> 21翻转为> 26 b / c,则不需要-1,这与我的下界映射到的位置无关,
BCS

26
我将解释为什么这是正确的:假设我要编写一个程序,输出一个从1到25的统一随机数流;为此,我只需按照答案中的代码返回5 *(rand5()-1)+ rand5()。现在,如果我想构建一个介于1到21之间的统一随机数流,如果我只使用第一个流并对它进行过滤,以便拒绝[22,25]中的数字,那么我也可以构建该流。接下来,如果我采用此流并将其过滤,以使每个元素x输出x%7 +1,则我将得到一个从1到7的均匀随机数流!很简单,不是吗?:D
帕加斯

6
而且您说对了,这归结为您想要的是具有无限制的最坏情况运行时的理想发行版,还是想要具有有限的运行时的不完美发行版。这是由于以下事实的结果:所有幂5都不能被7整除,或者等效地,如果您有5 ^ n个可能相等的长度为n的序列,则无法为每个序列分配从1到7的数字,使得每个1..7同样可能。
亚当·罗森菲尔德2009年

5
@JulesOlléon:假设有一个解决方案在恒定的时间内运行,在最坏的情况下,保证不超过N调用rand5()。然后,对的调用序列有5 ^ N个可能的结果rand5,每个输出结果为1-7。因此,如果将所有可能的呼叫序列k相加,其输出每个1≤k≤7,则输出的概率k为m / 5 ^ N,其中m是此类序列的数量。因此,m / 5 ^ N = 1/7,但是对于这个矛盾,没有可能的整数解(N,m)。
亚当·罗森菲尔德

4
@paxdiablo:您不正确。真正的RNG生成5的无穷序列的机会恰好为0,使用与以下事实类似的推理,即保证将硬币翻转无数次不会保证生成无数个连续的正面。这也意味着该代码永远循环的机率恰好为0(尽管有正向机会循环任意次数的迭代)。
BlueRaja-Danny Pflughoeft

153

除了第一个答案外,还要添加另一个答案。此答案尝试最大程度地减少对的rand5()每次呼叫的呼叫次数rand7(),以最大程度地利用随机性。也就是说,如果您认为随机性是一种宝贵的资源,我们希望在不丢弃任何随机位的情况下尽可能多地使用随机性。此答案也与Ivan答案中提出的逻辑有些相似之处。

随机变量是一个定义明确的量。对于这需要在N个随机变量具有相等概率(均匀分布)状态,熵为log 2 N.因此,rand5()具有熵的大约2.32193比特,并且rand7()具有大约熵2.80735比特。如果我们希望最大程度地利用随机性,则需要使用每次对的熵的全部2.32193熵rand5(),并将它们应用于生成对的每次调用所需的2.80735熵rand7()。因此,基本限制是,对to的rand5()每次调用,我们所能做的就是log(7)/ log(5)= 1.20906调用rand7()

旁注:除非另有说明,否则此答案中的所有对数均以2为底。 rand5()假定返回数字[0,4],并且rand7()假定返回数字[0,6]。将范围分别调整为[1,5]和[1,7]是微不足道的。

那么我们该怎么做呢?我们会生成一个介于0和1之间的无限精确的随机实数(假装我们可以实际计算并存储这样一个无限精确的数,稍后再解决)。我们可以通过在基体5产生其数字生成这样的数:我们挑选随机数0 a1 a2 a3 ...,其中每个数字一个i是通过向一个呼叫选择rand5()。例如,如果我们的RNG i为all 选择a = 1 i,那么忽略了它不是非常随机的事实,它将对应于实数1/5 + 1/5 2 + 1/5 3 + ... = 1/4(一个几何序列的和)。

好的,所以我们选择了一个介于0和1之间的随机实数。我现在声称这样一个随机数是均匀分布的。直观地讲,这很容易理解,因为每个数字都是统一选取的,并且数字是无限精确的。但是,对此的正式证明要更多一些,因为现在我们处理的是连续分布而不是离散分布,因此我们需要证明我们的数字位于区间[ ab] 中的概率等于该间隔b - a。证明留给读者练习)。

现在,我们从[0,1]范围内均匀选择了一个随机实数,我们需要将其转换为[0,6]范围内的一系列均匀随机数以生成的输出rand7()。我们如何做到这一点?与我们所做的恰好相反-我们将其转换为以7为底的无限精确的十进制,然后每个以7为底的数字将对应于的一个输出rand7()

以前面的示例为例,如果我们rand5()产生1的无限流,那么我们的随机实数将为1/4。将1/4转换为基数7,我们得到无穷小数0.15151515 ...,因此我们将产生输出1、5、1、5、1、5等。

好的,所以我们有了主要思想,但是还有两个问题:我们实际上无法计算或存储无限精确的实数,那么如何只处理其中的有限部分呢?其次,我们如何实际将其转换为基数7?

我们可以将0到1之间的数字转换为以7为底的一种方法如下:

  1. 乘以7
  2. 结果的整数部分是下一个基数7位
  3. 减去整数部分,仅保留小数部分
  4. 转到步骤1

为了解决无限精度的问题,我们计算了部分结果,并且还存储了结果的上限。也就是说,假设我们调用了rand5()两次,并且两次都返回了1。到目前为止,我们生成的数字为0.11(以5为底)。无论产生的无穷多个调用序列的其余部分是什么rand5(),我们正在生成的随机实数永远不会大于0.12:0.11≤0.11xyz ... <0.12始终是事实。

因此,跟踪当前的数字以及它可能取得的最大值,我们将两个数字都转换为基数7。如果它们在前k几个k数字上都一致,那么我们就可以安全地输出下一个数字-不管数字是多少。以5为基数的无限流,它们将永远不会影响以k7为基数的下一位数字!

这就是算法-生成的下一个输出rand7(),我们只生成rand5()所需数量的位数,以确保我们确定地知道在将随机实数转换为基数7时下一位的值。这是一个带有测试工具的Python实现:

import random

rand5_calls = 0
def rand5():
    global rand5_calls
    rand5_calls += 1
    return random.randint(0, 4)

def rand7_gen():
    state = 0
    pow5 = 1
    pow7 = 7
    while True:
        if state / pow5 == (state + pow7) / pow5:
            result = state / pow5
            state = (state - result * pow5) * 7
            pow7 *= 7
            yield result
        else:
            state = 5 * state + pow7 * rand5()
            pow5 *= 5

if __name__ == '__main__':
    r7 = rand7_gen()
    N = 10000
    x = list(next(r7) for i in range(N))
    distr = [x.count(i) for i in range(7)]
    expmean = N / 7.0
    expstddev = math.sqrt(N * (1.0/7.0) * (6.0/7.0))

    print '%d TRIALS' % N
    print 'Expected mean: %.1f' % expmean
    print 'Expected standard deviation: %.1f' % expstddev
    print
    print 'DISTRIBUTION:'
    for i in range(7):
        print '%d: %d   (%+.3f stddevs)' % (i, distr[i], (distr[i] - expmean) / expstddev)
    print
    print 'Calls to rand5: %d (average of %f per call to rand7)' % (rand5_calls, float(rand5_calls) / N)

请注意,rand7_gen()返回生成器,因为它的内部状态涉及将数字转换为基数7。测试工具调用next(r7)10000次以生成10000个随机数,然后测量其分布。仅使用整数数学,因此结果完全正确。

还要注意,这里的数字变得非常大,非常快。5和7的幂快速增长。因此,由于使用bignum算法,在生成大量随机数后性能将开始显着下降。但是请记住,我的目标是最大化随机位的使用,而不是最大化性能(尽管这是次要目标)。

在一次运行中,我rand5()对10000次调用进行了12091次调用,rand7()平均将log(7)/ log(5)调用的最小值平均为4个有效数字,并且输出结果是均匀的。

为了将此代码移植到没有内置任何大整数的语言中,您必须将本机整数类型的值限制为最大值,pow5并且pow7将其限制为本机整数类型的最大值-如果它们太大,请重置一切,重新开始。这将使rand5()每次呼叫的平均呼叫次数增加到rand7()很小,但希望即使对于32位或64位整数也不应增加太多。


7
+1是一个非常有趣的答案。是否有可能,而不是将其重置为某个特定值,而是简单地移开已使用的位,然后将其他位向上移动,并且基本上只保留将要使用的位?还是我错过了什么?
克里斯·卢茨

1
我不是100%确信,但是我相信,如果您这样做的话,您将使分布稍微偏斜(尽管我怀疑,如果没有数百万次的试验,这种偏斜是否可以测量)。
亚当·罗森菲尔德

FTW!我试图使bignum变小,但无法完成,因为5的幂与7的幂没有共同的因素!另外,善用yield关键字。做得太好了。
Eyal

2
非常好!我们能否在没有增长状态的情况下保留额外的熵?诀窍是要注意上限和下限始终都是有理数。我们可以对它们进行加,减和乘运算而不会损失精度。如果我们在35基础上完成所有工作,那么我们快到了。其余部分(乘以7并保留小数部分)留作练习。
伊恩

@adam必须参考“将pow5和pow7的值限制为您的本机整数类型的最大值”。我支持您第二个观点,即至少天真地这样做会扭曲分布。
催化剂

36

(我已经窃取了亚当·罗森菲尔德的答案,并使它的运行速度提高了约7%。)

假设rand5()返回具有相等分布的{0,1,2,3,4}中的一个,目标是返回具有相等分布的{0,1,2,3,4,5,6}。

int rand7() {
  i = 5 * rand5() + rand5();
  max = 25;
  //i is uniform among {0 ... max-1}
  while(i < max%7) {
    //i is uniform among {0 ... (max%7 - 1)}
    i *= 5;
    i += rand5(); //i is uniform {0 ... (((max%7)*5) - 1)}
    max %= 7;
    max *= 5; //once again, i is uniform among {0 ... max-1}
  }
  return(i%7);
}

我们一直在跟踪循环可以在变量中产生的最大值max。如果到目前为止的结果在max%7和max-1之间,则结果将均匀分布在该范围内。如果不是,则使用余数,该余数在0到max%7-1之间随机变化,并再次调用rand()以产生一个新的数字和一个新的最大值。然后,我们再次开始。

编辑:在此等式中,预期调用rand5()的次数为x:

x =  2     * 21/25
   + 3     *  4/25 * 14/20
   + 4     *  4/25 *  6/20 * 28/30
   + 5     *  4/25 *  6/20 *  2/30 * 7/10
   + 6     *  4/25 *  6/20 *  2/30 * 3/10 * 14/15
   + (6+x) *  4/25 *  6/20 *  2/30 * 3/10 *  1/15
x = about 2.21 calls to rand5()

2
1,000,000次尝试中分类的结果:1 = 47216;2 = 127444;3 = 141407; 4 = 221453; 5 = 127479; 6 = 167536;7 = 167465。正如你所看到的,分布在缺乏对于得到一个1的赔率
罗伯特ķ

2
@邪恶的跳蚤:我想你错了。您确定您用于测试的rand5()输入产生0-4而不是此解决方案中指定的1-5吗?
亚当·罗森菲尔德2009年

5
添加均匀分布的数字不会导致均匀分布的数字。实际上,您只需要对6个此类均匀分布的变量求和即可得出正态分布的合理近似值。
米奇小麦

2
@MitchWheat-实际上,如果可以以一种精确的方式生成每个可能的和,则将两个均匀分布的整数相加确实会产生一个均匀分布的随机整数。表达式中就是这种情况5 * rand5() + rand5()
泰德·霍普

28

算法:

7可以3比特的顺序表示

使用rand(5)用0或1随机填充每个位。
例如:调用rand(5)和

如果结果是1或2,
如果结果是4或5,则用0 填充该位;
如果结果是3,则用1填充该位,然后忽略并再次执行(拒绝)

这样,我们可以用0/1随机填充3位,从而得到1-7的数字。

编辑: 这似乎是最简单,最有效的答案,所以这里有一些代码:

public static int random_7() {
    int returnValue = 0;
    while (returnValue == 0) {
        for (int i = 1; i <= 3; i++) {
            returnValue = (returnValue << 1) + random_5_output_2();
        }
    }
    return returnValue;
}

private static int random_5_output_2() {
    while (true) {
        int flip = random_5();

        if (flip < 3) {
            return 0;
        }
        else if (flip > 3) {
            return 1;
        }
    }
}

1
停顿问题总是隐隐约约,因为一个差的随机数发生器可能在某个时刻只能产生很多三分。
亚历克斯·

“如果结果是1或2,如果结果是4或5,则用0填充该位。”接受1,2,4,5并拒绝3的逻辑是什么?你能解释一下吗?
gkns

@gkns没有逻辑,您可以让1和2的均值填充为0位,而让3和4的均值填充为1。重要的是,每个选项都有50%的发生机会,因此可以确保函数的随机性是至少与原始rand(5)函数一样随机。这是一个很好的解决方案!
Mo Beigi 2015年

这既不简单也不有效。每个random_7的randoms_5最多最多3个。此页面上的其他解决方案更接近于实际的最佳解决方案,约为2.2。
2015年

1
没关系,我错过了“ while returnValue == 0”部分
NicholasFolk

19
int randbit( void )
{
    while( 1 )
    {
        int r = rand5();
        if( r <= 4 ) return(r & 1);
    }
}

int randint( int nbits )
{
    int result = 0;
    while( nbits-- )
    {
        result = (result<<1) | randbit();
    }
    return( result );
}

int rand7( void )
{
    while( 1 )
    {
        int r = randint( 3 ) + 1;
        if( r <= 7 ) return( r );
    }
}

2
正确的解决方案,每个对rand7()的调用平均需要对rand5()进行30/7 = 4.29的调用。
亚当·罗森菲尔德

17
rand7() = (rand5()+rand5()+rand5()+rand5()+rand5()+rand5()+rand5())%7+1

编辑:那不是很有效。千分之二的价格(假设是完美的rand5)就减少了2分。桶得到:

value   Count  Error%
1       11158  -0.0035
2       11144  -0.0214
3       11144  -0.0214
4       11158  -0.0035
5       11172  +0.0144
6       11177  +0.0208
7       11172  +0.0144

通过转换为

n   Error%
10  +/- 1e-3,
12  +/- 1e-4,
14  +/- 1e-5,
16  +/- 1e-6,
...
28  +/- 3e-11

似乎每增加2个数量级

顺便说一句:上面的错误表不是通过抽样生成的,而是通过以下递归关系生成的:

p[x,n]是调用时output=x可以发生的方式数量。nrand5

  p[1,1] ... p[5,1] = 1
  p[6,1] ... p[7,1] = 0

  p[1,n] = p[7,n-1] + p[6,n-1] + p[5,n-1] + p[4,n-1] + p[3,n-1]
  p[2,n] = p[1,n-1] + p[7,n-1] + p[6,n-1] + p[5,n-1] + p[4,n-1]
  p[3,n] = p[2,n-1] + p[1,n-1] + p[7,n-1] + p[6,n-1] + p[5,n-1]
  p[4,n] = p[3,n-1] + p[2,n-1] + p[1,n-1] + p[7,n-1] + p[6,n-1]
  p[5,n] = p[4,n-1] + p[3,n-1] + p[2,n-1] + p[1,n-1] + p[7,n-1]
  p[6,n] = p[5,n-1] + p[4,n-1] + p[3,n-1] + p[2,n-1] + p[1,n-1]
  p[7,n] = p[6,n-1] + p[5,n-1] + p[4,n-1] + p[3,n-1] + p[2,n-1]

8
这不是均匀分布。它非常接近统一,但并非完美统一。
亚当·罗森菲尔德

啊! 骰子和7的。如果您要说我错了,则不应将证明留给读者练习。
BCS

45
不均匀的证明很简单:随机性可以有5 ^ 7种可能的方式,并且由于5 ^ 7不是7的倍数,所以不可能全部7个和都是同等的。(基本上,它归结为7相对于5的质数,或者等效为1/7不是以5为底的十进制数。)实际上,在这种约束下它甚至不是“最统一的”:直接计算表明5 ^ 7 = 78125总和,您获得值1到7的次数是{1:11145,2:11120,3:11120,4:11145,5:11190,6:11215,7:11190}。
ShreevatsaR

@ShreevatsaR那么,如果我们不是将rand5()的总和取七次,而是将它乘以5 * 7的结果怎么办?35 ^ 7%7 = 35 ^ 5%7 = 0。
KBA

4
@KristianAntonsen:多少次执行rand5(),您都无法获得均匀分布。如果执行N次,则有5 ^ N个可能的输出,不能被7整除。(如果执行35次,则有5 ^ 35,而不是35 ^ 7。)您会越来越近统一使用的大量呼叫(可以是任意数量,不必被7整除),但是恕我直言,而不是对rand()使用大量呼叫,您还可以使用概率最佳答案中的算法,该算法给出了精确的均匀分布,并且对rand()的预期调用次数很少。
ShreevatsaR 2012年

15
int ans = 0;
while (ans == 0) 
{
     for (int i=0; i<3; i++) 
     {
          while ((r = rand5()) == 3){};
          ans += (r < 3) >> i
     }
}

2
正确的解决方案,每个对rand7()的调用平均需要对rand5()进行30/7 = 4.29的调用。
亚当·罗森菲尔德

3
需要左移才能使算法ans += (r < 3) << i
生效

13

下面使用在{1,2,3,4,5}上产生均匀分布的随机数发生器在{1,2,3,4,5,6,7}上产生均匀分布。代码很杂乱,但是逻辑很清楚。

public static int random_7(Random rg) {
    int returnValue = 0;
    while (returnValue == 0) {
        for (int i = 1; i <= 3; i++) {
            returnValue = (returnValue << 1) + SimulateFairCoin(rg);
        }
    }
    return returnValue;
}

private static int SimulateFairCoin(Random rg) {
    while (true) {
        int flipOne = random_5_mod_2(rg);
        int flipTwo = random_5_mod_2(rg);

        if (flipOne == 0 && flipTwo == 1) {
            return 0;
        }
        else if (flipOne == 1 && flipTwo == 0) {
            return 1;
        }
    }
}

private static int random_5_mod_2(Random rg) {
    return random_5(rg) % 2;
}

private static int random_5(Random rg) {
    return rg.Next(5) + 1;
}    

2
正确的解决方案(使您走在曲线的前面),尽管效率不高。这使得每次公平硬币翻转平均有25/6 = 4.17次对random_5_mod_2的调用,而每次对random_7()的总平均有100/7 = 14.3次对random_5()的调用。
亚当·罗森菲尔德

该解决方案相对于其他解决方案的优势在于,可以轻松扩展以产生任何其他均匀分布的范围。只需随机选择每个位,重新滚动无效值即可(例如当前解决方案中产生8个数字的0值)。
DenTheMan 2011年

1
可以无限循环,等等
robermorales

1
@robermorales:极不可能。
杰森

13
int rand7() {
    int value = rand5()
              + rand5() * 2
              + rand5() * 3
              + rand5() * 4
              + rand5() * 5
              + rand5() * 6;
    return value%7;
}

与选择的解决方案不同,该算法将在恒定时间内运行。但是,它对rand5的调用确实比所选解决方案的平均运行时间多2次。

请注意,此生成器不是完美的(数字0比其他任何数字都有0.0064%的机会),但是对于大多数实际目的而言,保证恒定时间可能会超过此误差。

说明

该解决方案源于数字15,624可被7整除的事实,因此,如果我们可以随机且均匀地生成0到15,624之间的数字,然后采用mod 7,我们可以获得近似均匀的rand7生成器。通过将rand5滚动6次并使用它们形成以5为基数的数字,可以统一生成0到15624之间的数字:

rand5 * 5^5 + rand5 * 5^4 + rand5 * 5^3 + rand5 * 5^2 + rand5 * 5 + rand5

但是,mod 7的属性使我们可以简化方程式:

5^5 = 3 mod 7
5^4 = 2 mod 7
5^3 = 6 mod 7
5^2 = 4 mod 7
5^1 = 5 mod 7

所以

rand5 * 5^5 + rand5 * 5^4 + rand5 * 5^3 + rand5 * 5^2 + rand5 * 5 + rand5

变成

rand5 * 3 + rand5 * 2 + rand5 * 6 + rand5 * 4 + rand5 * 5 + rand5

理论

15,624不是随机选择的,而是可以使用费马小定理发现的,该定理指出如果p是素数,则

a^(p-1) = 1 mod p

所以这给了我们

(5^6)-1 = 0 mod 7

(5 ^ 6)-1等于

4 * 5^5 + 4 * 5^4 + 4 * 5^3 + 4 * 5^2 + 4 * 5 + 4

这是一个以5为基数的数字,因此我们可以看到,该方法可用于从任何随机数生成器转换为任何其他随机数生成器。尽管在使用指数p-1时总是会向0产生小的偏差。

为了概括这种方法并使其更加准确,我们可以使用如下函数:

def getRandomconverted(frm, to):
    s = 0
    for i in range(to):
        s += getRandomUniform(frm)*frm**i
    mx = 0
    for i in range(to):
        mx = (to-1)*frm**i 
    mx = int(mx/to)*to # maximum value till which we can take mod
    if s < mx:
        return s%to
    else:
        return getRandomconverted(frm, to)

1
该生成器是准确的,但并非完全均匀。要看到这一点,请考虑以下事实:[0,15624]中的统一生成器可能有15625个可能的结果,不能被7整除。这给数字0带来了偏差(0有2233/15625的机会,而其他只是2232/15625)。毕竟,乍一看使用费马小定理似乎是正确的,但它说的是(5 ^ 6)%7 = 1,而不是(5 ^ 6)%7 = 0。后者对于任何指数显然都是不可能的,因为5和7都是素数。我认为这仍然是可以接受的解决方案,并且我已经编辑了您的帖子以反映这一点。
飞行员

12

这里允许做作业吗?

此函数执行粗略的“以5为底”数学运算,以生成介于0和6之间的数字。

function rnd7() {
    do {
        r1 = rnd5() - 1;
        do {
            r2=rnd5() - 1;
        } while (r2 > 1);
        result = r2 * 5 + r1;
    } while (result > 6);
    return result + 1;
}

3
正确的解决方案(使您走在曲线的前面),尽管效率不高。对于每个对rnd7()的调用,平均要对rnd5()进行5次调用。
亚当·罗森菲尔德

需要更多解释
巴里

1
@Barry-首先,您不能只将两个随机数加在一起,就不能得到线性解(考虑一对骰子)。现在考虑“ Base 5”:00、01、02、03、04、10、11。以5为基数的0-6。因此,我们只需要生成以5为基数的2位数字,并将它们加起来就可以了。得到一个在范围内的。这就是r2 * 5 + r1的功能。在R2> 1个循环是有,因为我们永远不会想的> 1.高数字
威尔哈同

此解决方案不会生成均匀的分布。数字1和7只能以一种方式生成,但是2到6可以分别以两种方式生成:r1等于数字负1且r2等于0或r1等于数字负2且r2等于1。因此2至6将平均两倍经常返回为1或7
泰德霍普

12

如果我们考虑尝试给出最有效答案的附加约束,即给定一个输入流,则I长度m为1-5 的均匀分布整数的输出流O,相对于最长长度的1-7的均匀分布整数的流对m,说L(m)

分析此问题的最简单方法是将流I和I分别O视为5元和7元数。这是通过主要答案的想法来实现的,并且对stream a1, a2, a3,... -> a1+5*a2+5^2*a3+..同样如此O

然后,如果我们截取一部分输入流,长度为m choose n s.t. 5^m-7^n=cc>0则其中和尽可能小。再有就是从长度为m为整数输入流的均匀映射从15^m和从整数到另一均匀地图从1 7^n到长度的输出流n,其中我们可能必须从输入流失去一些情况下当所述映射整数超过7^n

因此,这给出了一个值L(m)围绕m (log5/log7)这大约是.82m

上述分析的难点在于方程式难以5^m-7^n=c精确求解,以及从1到的统一值5^m超过7^n而失去效率的情况。

问题是,如何才能达到m的最佳可能值(log5 / log7)。例如,当这个数字接近整数时,我们可以找到一种方法来实现输出值的这个精确整数吗?

如果5^m-7^n=c从输入流中提取了有效值,则我们将有效地从生成一个统一的随机数0(5^m)-1并且不使用任何大于的值7^n。但是,这些值可以挽救并再次使用。它们有效地生成从1到的统一数字序列5^m-7^n。因此,我们可以尝试使用它们并将它们转换为7进制数,以便我们可以创建更多输出值。

如果我们T7(X)random(1-7)整数的输出序列的平均长度设为均匀大小输入X,并假定为5^m=7^n0+7^n1+7^n2+...+7^nr+s, s<7

然后,T7(5^m)=n0x7^n0/5^m + ((5^m-7^n0)/5^m) T7(5^m-7^n0)由于我们有一个长度为no的序列,概率为7 ^ n0 / 5 ^ m,剩余的序列的5^m-7^n0概率为(5^m-7^n0)/5^m)

如果我们继续替换,我们将获得:

T7(5^m) = n0x7^n0/5^m + n1x7^n1/5^m + ... + nrx7^nr/5^m  = (n0x7^n0 + n1x7^n1 + ... + nrx7^nr)/5^m

因此

L(m)=T7(5^m)=(n0x7^n0 + n1x7^n1 + ... + nrx7^nr)/(7^n0+7^n1+7^n2+...+7^nr+s)

另一种放置方式是:

If 5^m has 7-ary representation `a0+a1*7 + a2*7^2 + a3*7^3+...+ar*7^r
Then L(m) = (a1*7 + 2a2*7^2 + 3a3*7^3+...+rar*7^r)/(a0+a1*7 + a2*7^2 + a3*7^3+...+ar*7^r)

最好的情况是我原来在哪里5^m=7^n+s,哪里s<7

然后T7(5^m) = nx(7^n)/(7^n+s) = n+o(1) = m (Log5/Log7)+o(1)像以前一样。

最坏的情况是我们只能找到k和st 5 ^ m = kx7 + s。

Then T7(5^m) = 1x(k.7)/(k.7+s) = 1+o(1)

其他情况介于两者之间。看到我们对于非常大的m可以做的很好,即我们能得到多大的误差项,将会很有趣:

T7(5^m) = m (Log5/Log7)+e(m)

e(m) = o(1)总体看来,这似乎是不可能的,但希望我们能证明e(m)=o(m)

然后,整个过程取决于的5^m各种值的7进制数字的分布m

我敢肯定,有很多理论可以解决这个问题,我可能会看一下,并在某些时候进行汇报。


+2(如果可以的话)-这是唯一的好答案(而不是足够的答案)。您将获得第二个最佳答案,该答案将适合32位整数。
Rex Kerr

10

这是Adam的answer的有效Python实现。

import random

def rand5():
    return random.randint(1, 5)

def rand7():
    while True:
        r = 5 * (rand5() - 1) + rand5()
        #r is now uniformly random between 1 and 25
        if (r <= 21):
            break
    #result is now uniformly random between 1 and 7
    return r % 7 + 1

我喜欢将我正在查看的算法投入Python,以便可以与它们一起玩,以为我希望将其发布在这里,希望它对外面的人有用,而不是花很长时间一起投入。


不,这与我的回答完全不同。您循环21次,并丢弃前20次迭代的结果。您还使用rand4()和rand5()作为输入,这显然违反了仅使用rand5()的规则。最后,您将产生非均匀分布。
亚当·罗森菲尔德2009年

对于那个很抱歉。当我仔细研究这个问题时,我非常疲倦,以至于我完全误解了您的算法。我实际上把它扔进了Python,因为我不明白为什么你要循环21次。现在变得更加有意义。我做了random.randint(1,4)的速记,但是我想你是正确的,这与问题的实质背道而驰。我已经更正了代码。
詹姆斯·麦克马洪

@robermorales-正如Adam Rosenfeld在他的回答中解释的那样,每个在[1,7]上给出真实均匀分布的解决方案都将涉及某种可能无限的接受-拒绝循环。(但是,如果rand5()PRNG不错,则循环将不会是无限的,因为最终5*(rand5() - 1) + rand5()肯定会是<=21。)
Ted Hopp

10

为什么不简单呢?

int random7() {
  return random5() + (random5() % 3);
}

由于取模,在此解决方案中获得1和7的机会较低,但是,如果您只是想要一种快速且易读的解决方案,这就是方法。


13
这不会产生均匀的分布。这可以得出数字0-6,其概率为2 / 25、4 / 25、5 / 25、5 / 25、5 / 25、3 / 25、1 / 25,可以通过计算所有25种可能的结果来验证。
亚当·罗森菲尔德

8

假设rand(n)的 意思是“从0n-1均匀分布的随机整数”,这是一个使用Python randrand的代码示例,具有这种效果。它仅使用randint(5)和常量来产生randint(7)的效果。有点傻,实际上

from random import randint
sum = 7
while sum >= 7:
    first = randint(0,5)   
    toadd = 9999
    while toadd>1:
        toadd = randint(0,5)
    if toadd:
        sum = first+5
    else:
        sum = first

assert 7>sum>=0 
print sum

1
@robermorales因为Python没有do ... while。它可能是1337,或12345,或任何数量的> 1
门把手

8

Adam Rosenfield正确答案的前提是:

  • X = 5 ^ n(在他的情况下:n = 2)
  • 操纵n rand5个电话以获取号码 范围[1,x]内 y
  • z =((int)(x / 7))* 7
  • 如果y> z,请重试。否则返回y%7 +1

当n等于2时,您有4种扔掉的可能性:y = {22,23,24,25}。如果您使用n等于6,则只有1个丢球:y = {15625}。

5 ^ 6 = 15625
= 15625 7 * 2232 = 15624

您再拨打rand5次。但是,获得扔掉值(或无限循环)的机会要低得多。如果有一种方法无法获得y的抛弃值,则尚未找到。


1
可证明的情况是,没有不丢掉值的情况-如果不丢掉值,则5 ^ n和7 ^ m将有一个共同的因素。但是它们是素数(的力量),所以不是。
Rex Kerr

8

这是我的答案:

static struct rand_buffer {
  unsigned v, count;
} buf2, buf3;

void push (struct rand_buffer *buf, unsigned n, unsigned v)
{
  buf->v = buf->v * n + v;
  ++buf->count;
}

#define PUSH(n, v)  push (&buf##n, n, v)

int rand16 (void)
{
  int v = buf2.v & 0xf;
  buf2.v >>= 4;
  buf2.count -= 4;
  return v;
}

int rand9 (void)
{
  int v = buf3.v % 9;
  buf3.v /= 9;
  buf3.count -= 2;
  return v;
}

int rand7 (void)
{
  if (buf3.count >= 2) {
    int v = rand9 ();

    if (v < 7)
      return v % 7 + 1;

    PUSH (2, v - 7);
  }

  for (;;) {
    if (buf2.count >= 4) {
      int v = rand16 ();

      if (v < 14) {
        PUSH (2, v / 7);
        return v % 7 + 1;
      }

      PUSH (2, v - 14);
    }

    // Get a number between 0 & 25
    int v = 5 * (rand5 () - 1) + rand5 () - 1;

    if (v < 21) {
      PUSH (3, v / 7);
      return v % 7 + 1;
    }

    v -= 21;
    PUSH (2, v & 1);
    PUSH (2, v >> 1);
  }
}

它比其他的稍微复杂一些,但是我相信它可以最大程度地减少对rand5的调用。与其他解决方案一样,它可以长时间循环的可能性很小。


这产生的分布与其他解决方案没有太大不同,但是具有不必要的复杂性。如果数字是真正随机的,它还会遭受证明为错误的不确定性永远循环的可能性。我仍然认为那些产生均匀分布稍差(尽管仍然远远超过适当的分布)但保证确定性行为的那些更好。
paxdiablo

@Pax:请就此如何产生不均匀分布给我启发。我对代码的分析以及我自己的测试表明,这会产生均匀的分布。正如我们之前讨论的那样,既不可能产生完全均匀的分布又要保证运行时间具有恒定的时间上限。
亚当·罗森菲尔德2009年


6

只要没有7种可能性可供选择,请绘制另一个随机数,该随机数会将可能性数乘以5。在Perl中:

$num = 0;
$possibilities = 1;

sub rand7
{
  while( $possibilities < 7 )
  {
    $num = $num * 5 + int(rand(5));
    $possibilities *= 5;
  }
  my $result = $num % 7;
  $num = int( $num / 7 );
  $possibilities /= 7;
  return $result;
}

您的分配不均匀,至少在第一次通话时。确实,$possibilities必须始终增长到25才能退出循环并返回。因此,您的第一个结果是[0-124] % 7,由于125 % 7 != 0(实际上是6),它不是均匀分布的。
bernard paulus 2013年

6

我不喜欢从1开始的范围,所以我将从0开始:-)

unsigned rand5()
{
    return rand() % 5;
}

unsigned rand7()
{
    int r;

    do
    {
        r =         rand5();
        r = r * 5 + rand5();
        r = r * 5 + rand5();
        r = r * 5 + rand5();
        r = r * 5 + rand5();
        r = r * 5 + rand5();
    } while (r > 15623);

    return r / 2232;
}

这是赢家。这将以相等的概率产生所有7个结果。from collections import defaultdict def r7(n): if not n: yield [] else: for i in range(1, 6): for j in r7(n-1): yield [i] + j def test_r7(): d = defaultdict(int) for x in r7(6): s = (((((((((x[5] * 5) + x[4]) * 5) + x[3]) * 5) + x[2]) * 5) + x[1]) * 5) + x[0] if s <= 15623: d[s % 7] += 1 print d
hughdbrown 2010年

5

你去了,统一分配和零rand5电话。

def rand7:
    seed += 1
    if seed >= 7:
        seed = 0
    yield seed

需要预先设置种子。


5

我知道已经回答了,但这似乎行得通,但是我不能告诉你它是否有偏差。我的“测试”表明至少是合理的。

也许亚当·罗森菲尔德会好心地发表评论?

我的想法(天真?)是这样的:

累积rand5直到有足够的随机位来生成rand7。最多需要2 rand5。要获得rand7数字,我使用累计值mod 7。

为了避免累加器溢出,并且由于累加器为mod 7,所以我采用累加器的mod 7:

(5a + rand5) % 7 = (k*7 + (5a%7) + rand5) % 7 = ( (5a%7) + rand5) % 7

rand7()函数如下:

(我让rand5的范围为0-4,rand7同样为0-6。)

int rand7(){
  static int    a=0;
  static int    e=0;
  int       r;
  a = a * 5 + rand5();
  e = e + 5;        // added 5/7ths of a rand7 number
  if ( e<7 ){
    a = a * 5 + rand5();
    e = e + 5;  // another 5/7ths
  }
  r = a % 7;
  e = e - 7;        // removed a rand7 number
  a = a % 7;
  return r;
}

编辑:增加了1亿次试验的结果。

'Real'rand函数mod 5或7

rand5:avg = 1.999802 0:20003944 1:19999889 2:20003690 3:19996938 4:19995539 ​​rand7:avg = 3.000111 0:14282851 1:14282879 2:14284554 3:14288546 4:14292388 5:14288736 6:14280046

我的rand7

平均看起来还可以,数字分布也可以。

兰特:avg = 3.000080 0:14288793 1:14280135 2:14287848 3:14285277 4:14286341 5:14278663 6:14292943


您可能应该看一下顺序相关。我认为,如果您采用连续的配对(每个“随机”数字与其前身配对),那么您可能会发现令人惊讶的事情。您还没有解释为什么要保持分发无论如何都一致。一个正常运行的程序通常应先说明其工作原理。
伊恩,

顺序相关是否适用于其中许多解决方案?
philcolbourn

顺序相关会应用于这些解决方案吗?自从我尝试这样做以来已经有一段时间了,我以为我已经解释了。现在看,好像我正在从rand5的池中累积随机位,请确保在提取足够数量以生成rand7编号之前已经累积了足够的值,并确保我不会使累加器溢出。
philcolbourn

4

上面引用了一些精美的算法,但这是一种处理方法,尽管它可能是回旋处。我假设从0生成的值。

R2 =给出小于2的值的随机数生成器(样本空间= {0,1})
R8 =给出小于8的值的随机数生成器(样本空间= {0,1,2,3,4,5,5,6,7 })

为了从R2生成R8,您将运行R2三次,并将所有3次运行的组合结果用作具有3位数字的二进制数。这是R2运行三次时的值范围:

0 0 0-> 0


1 1 1-> 7

现在要从R8生成R7,如果R7返回7,我们只需再次运行R7:

int R7() {
  do {
    x = R8();
  } while (x > 6)
  return x;
}

环形交叉路口解决方案是从R5生成R2(就像我们从R8生成R7一样),然后从R2生成R8,然后从R8生成R7。


像其他许多方法一样,这种方法每次R7调用可能要花费任意长时间,因为您可以从R8中获得一长串的7。
Alex North-Keys'4

4

这是一个完全适合整数且在最佳值的4%以内的解决方案(即,{0..6}中的每个数字在{0..4}中使用1.26个随机数)。代码在Scala中,但是数学在任何语言中都应该相当清楚:您利用7 ^ 9 + 7 ^ 8非常接近5 ^ 11的事实。因此,您选择以5为底的11位数字,然后将其解释为以7为底的9位数字(如果它在范围内)(给出9个以7为底的数字),或者解释为8位数字(如果超过9位数字),等等。 ::

abstract class RNG {
  def apply(): Int
}

class Random5 extends RNG {
  val rng = new scala.util.Random
  var count = 0
  def apply() = { count += 1 ; rng.nextInt(5) }
}

class FiveSevener(five: RNG) {
  val sevens = new Array[Int](9)
  var nsevens = 0
  val to9 = 40353607;
  val to8 = 5764801;
  val to7 = 823543;
  def loadSevens(value: Int, count: Int) {
    nsevens = 0;
    var remaining = value;
    while (nsevens < count) {
      sevens(nsevens) = remaining % 7
      remaining /= 7
      nsevens += 1
    }
  }
  def loadSevens {
    var fivepow11 = 0;
    var i=0
    while (i<11) { i+=1 ; fivepow11 = five() + fivepow11*5 }
    if (fivepow11 < to9) { loadSevens(fivepow11 , 9) ; return }
    fivepow11 -= to9
    if (fivepow11 < to8) { loadSevens(fivepow11 , 8) ; return }
    fivepow11 -= to8
    if (fivepow11 < 3*to7) loadSevens(fivepow11 % to7 , 7)
    else loadSevens
  }
  def apply() = {
    if (nsevens==0) loadSevens
    nsevens -= 1
    sevens(nsevens)
  }
}

如果将测试粘贴到解释器中(实际上是REPL),则会得到:

scala> val five = new Random5
five: Random5 = Random5@e9c592

scala> val seven = new FiveSevener(five)
seven: FiveSevener = FiveSevener@143c423

scala> val counts = new Array[Int](7)
counts: Array[Int] = Array(0, 0, 0, 0, 0, 0, 0)

scala> var i=0 ; while (i < 100000000) { counts( seven() ) += 1 ; i += 1 }
i: Int = 100000000

scala> counts
res0: Array[Int] = Array(14280662, 14293012, 14281286, 14284836, 14287188,
14289332, 14283684)

scala> five.count
res1: Int = 125902876

分布良好且平坦(每个仓中大约有10k的10/8的1/7的10k左右,这是近似高斯分布所期望的)。


3

通过使用滚动总计,您可以

  • 保持平等的分配;和
  • 不必牺牲随机序列中的任何元素。

这两个问题都是简单rand(5)+rand(5)...类型解决方案的问题。以下Python代码显示了如何实现它(大多数是证明发行版)。

import random
x = []
for i in range (0,7):
    x.append (0)
t = 0
tt = 0
for i in range (0,700000):
    ########################################
    #####            qq.py             #####
    r = int (random.random () * 5)
    t = (t + r) % 7
    ########################################
    #####       qq_notsogood.py        #####
    #r = 20
    #while r > 6:
        #r =     int (random.random () * 5)
        #r = r + int (random.random () * 5)
    #t = r
    ########################################
    x[t] = x[t] + 1
    tt = tt + 1
high = x[0]
low = x[0]
for i in range (0,7):
    print "%d: %7d %.5f" % (i, x[i], 100.0 * x[i] / tt)
    if x[i] < low:
        low = x[i]
    if x[i] > high:
        high = x[i]
diff = high - low
print "Variation = %d (%.5f%%)" % (diff, 100.0 * diff / tt)

此输出显示结果:

pax$ python qq.py
0:   99908 14.27257
1:  100029 14.28986
2:  100327 14.33243
3:  100395 14.34214
4:   99104 14.15771
5:   99829 14.26129
6:  100408 14.34400
Variation = 1304 (0.18629%)

pax$ python qq.py
0:   99547 14.22100
1:  100229 14.31843
2:  100078 14.29686
3:   99451 14.20729
4:  100284 14.32629
5:  100038 14.29114
6:  100373 14.33900
Variation = 922 (0.13171%)

pax$ python qq.py
0:  100481 14.35443
1:   99188 14.16971
2:  100284 14.32629
3:  100222 14.31743
4:   99960 14.28000
5:   99426 14.20371
6:  100439 14.34843
Variation = 1293 (0.18471%)

一个简单的方法rand(5)+rand(5)(忽略返回值大于6的情况)的典型变化为18%,是上述方法的100倍

pax$ python qq_notsogood.py
0:   31756 4.53657
1:   63304 9.04343
2:   95507 13.64386
3:  127825 18.26071
4:  158851 22.69300
5:  127567 18.22386
6:   95190 13.59857
Variation = 127095 (18.15643%)

pax$ python qq_notsogood.py
0:   31792 4.54171
1:   63637 9.09100
2:   95641 13.66300
3:  127627 18.23243
4:  158751 22.67871
5:  126782 18.11171
6:   95770 13.68143
Variation = 126959 (18.13700%)

pax$ python qq_notsogood.py
0:   31955 4.56500
1:   63485 9.06929
2:   94849 13.54986
3:  127737 18.24814
4:  159687 22.81243
5:  127391 18.19871
6:   94896 13.55657
Variation = 127732 (18.24743%)

并且,根据Nixuz的建议,我已经清理了脚本,因此您可以提取并使用这些rand7...内容:

import random

# rand5() returns 0 through 4 inclusive.

def rand5():
    return int (random.random () * 5)

# rand7() generator returns 0 through 6 inclusive (using rand5()).

def rand7():
    rand7ret = 0
    while True:
        rand7ret = (rand7ret + rand5()) % 7
        yield rand7ret

# Number of test runs.

count = 700000

# Work out distribution.

distrib = [0,0,0,0,0,0,0]
rgen =rand7()
for i in range (0,count):
    r = rgen.next()
    distrib[r] = distrib[r] + 1

# Print distributions and calculate variation.

high = distrib[0]
low = distrib[0]
for i in range (0,7):
    print "%d: %7d %.5f" % (i, distrib[i], 100.0 * distrib[i] / count)
    if distrib[i] < low:
        low = distrib[i]
    if distrib[i] > high:
        high = distrib[i]
diff = high - low
print "Variation = %d (%.5f%%)" % (diff, 100.0 * diff / count)

2
不好意思,我再改一下。假设在序列的某个点产生了一个特定的x,则序列中的下一个数字只能产生7个数字中的5个。真正的RNG将使所有样本彼此独立,但是在这种情况下,它们显然不是相互独立的。
亚当·罗森菲尔德

3
确实,原始问题没有指定输入和输出函数是否生成独立且分布均匀的(iid)样本,但是我认为可以合理地预期,如果输入rand5()是iid,那么输出rand7()也应该是iid。如果您认为这不合理,请使用非iid RNG玩得开心。
亚当·罗森菲尔德

1
那么,大学的数学家怎么说呢?
亚当·罗森菲尔德

1
该解决方案显然是无效的。显然,每次调用rand7都需要多次调用rand5(平均),而此解决方案则不需要。因此,根据任何理智的随机定义,结果都不能是随机的。
克里斯·苏特

1
@Pax在函数的每次迭代中,它只能返回五个不同值之一(尽管范围为0-6)。第一次迭代只能返回0-4范围内的数字。因此,应该清楚的是,尽管您的函数可能具有均匀的分布,但样本并不是独立的,即它们是相关的,这在随机数生成器中不是您想要的。
克里斯·苏特

3

这个答案更多是通过Rand5函数获得最大熵的实验。因此,t尚不清楚,几乎可以肯定比其他实现要慢得多。

假设0-4的均匀分布和0-6的均匀分布:

public class SevenFromFive
{
  public SevenFromFive()
  {
    // this outputs a uniform ditribution but for some reason including it 
    // screws up the output distribution
    // open question Why?
    this.fifth = new ProbabilityCondensor(5, b => {});
    this.eigth = new ProbabilityCondensor(8, AddEntropy);
  } 

  private static Random r = new Random();
  private static uint Rand5()
  {
    return (uint)r.Next(0,5);
  }

  private class ProbabilityCondensor
  {
    private readonly int samples;
    private int counter;
    private int store;
    private readonly Action<bool> output;

    public ProbabilityCondensor(int chanceOfTrueReciprocal,
      Action<bool> output)
    {
      this.output = output;
      this.samples = chanceOfTrueReciprocal - 1;  
    }

    public void Add(bool bit)
    {
      this.counter++;
      if (bit)
        this.store++;   
      if (counter == samples)
      {
        bool? e;
        if (store == 0)
          e = false;
        else if (store == 1)
          e = true;
        else
          e = null;// discard for now       
        counter = 0;
        store = 0;
        if (e.HasValue)
          output(e.Value);
      }
    }
  }

  ulong buffer = 0;
  const ulong Mask = 7UL;
  int bitsAvail = 0;
  private readonly ProbabilityCondensor fifth;
  private readonly ProbabilityCondensor eigth;

  private void AddEntropy(bool bit)
  {
    buffer <<= 1;
    if (bit)
      buffer |= 1;      
    bitsAvail++;
  }

  private void AddTwoBitsEntropy(uint u)
  {
    buffer <<= 2;
    buffer |= (u & 3UL);    
    bitsAvail += 2;
  }

  public uint Rand7()
  {
    uint selection;   
    do
    {
      while (bitsAvail < 3)
      {
        var x = Rand5();
        if (x < 4)
        {
          // put the two low order bits straight in
          AddTwoBitsEntropy(x);
          fifth.Add(false);
        }
        else
        { 
          fifth.Add(true);
        }
      }
      // read 3 bits
      selection = (uint)((buffer & Mask));
      bitsAvail -= 3;     
      buffer >>= 3;
      if (selection == 7)
        eigth.Add(true);
      else
        eigth.Add(false);
    }
    while (selection == 7);   
    return selection;
  }
}

每次对Rand5的调用添加到缓冲区的位数为4/5 * 2,因此为1.6。如果包含的1/5概率值增加了0.05,则增加了1.65,但请参阅代码中的注释,我必须禁用此注释。

调用Rand7消耗的位= 3 + 1/8 *(3 + 1/8 *(3 + 1/8 *(...
这是3 + 3/8 + 3/64 + 3/512 ...约3.42

通过从七位数中提取信息,我每次调用可以回收1/8 * 1/7位,因此约为0.018

这样,每个调用的净消耗为3.4位,这意味着每个Rand7的Rand5调用比率为2.125。最佳值为2.1。

我猜想这种做法是显著慢于这里的许多其他的人,除非调用Rand5的成本是非常昂贵的(说呼唤熵的一些外部源)。


除了一些简单的错误之外,您的解决方案看起来是正确的:“ if(count> 1)”应为“ if(count <= 1)”,此后不久出现的“ i ++”应位于其前的花括号内。我不确定BitsSet()是否正确,但这有点不相关。
亚当·罗森菲尔德

总体而言,您的功能很难理解。这确实让稍微更好地利用熵比否则可能,在更复杂的成本。也没有理由在第一个调用中最初用35个随机位填充缓冲区,而3个就足够了。
亚当·罗森菲尔德

我更正了<=谢谢,但i ++确实应该在那里。它应该在零和1的情况下发生(分别向缓冲区添加1或零)。这绝对不是我建议使用的方法,它非常复杂。我只是很感兴趣,我能接近问题固有的理论熵极限...感谢您的反馈。具有讽刺意味的是,第一次调用时缓冲区的填充是为了简化编写:)
ShuggyCoUk,2009年

我对其进行了重新设计,以使其更容易理解(以速度为代价),但也使其正确。这不是最佳选择,由于某些原因,即使1/5位的计数相同,也会引起问题。
ShuggyCoUk,2009年

3

在PHP中

function rand1to7() {
    do {
        $output_value = 0;
        for ($i = 0; $i < 28; $i++) {
            $output_value += rand1to5();
        }
    while ($output_value != 140);
    $output_value -= 12;
    return floor($output_value / 16);
}

循环产生一个介于16和127之间的随机数,除以16以创建一个介于1和7.9375之间的浮点数,然后向下舍入以获得一个介于1和7之间的整数。如果我没记错的话,有16/112的机会7个结果中的任何一个。


尽管可能没有条件循环,并且取模而不是底数,这是一个类似的更简单答案。我只是现在不能处理数字。
dqhendricks 2011年

3
extern int r5();

int r7() {
    return ((r5() & 0x01) << 2 ) | ((r5() & 0x01) << 1 ) | (r5() & 0x01);
}

问题:这会在0-7(而不是0-6)范围内非均匀地返回。确实,您可以7 = 111bp(7) = 8 / 125
Bernard paulus

3

我想我有四个答案,两个给出了像@Adam Rosenfield一样的精确解决方案但是没有无限循环问题,另外两个给出了几乎完美的解决方案,但是比第一个实现起来更快。

最佳的精确解决方案需要7个调用rand5,但为了理解,请继续进行。

方法1-精确

亚当的回答的强项是它给出了一个完美的均匀分布,并且极有可能(21/25)仅需要两次调用rand5()。但是,最坏的情况是无限循环。

下面的第一个解决方案还提供了一个完美的均匀分布,但是总共需要42次调用rand5。没有无限循环。

这是一个R实现:

rand5 <- function() sample(1:5,1)

rand7 <- function()  (sum(sapply(0:6, function(i) i + rand5() + rand5()*2 + rand5()*3 + rand5()*4 + rand5()*5 + rand5()*6)) %% 7) + 1

对于不熟悉R的人,这里是简化版本:

rand7 = function(){
  r = 0 
  for(i in 0:6){
    r = r + i + rand5() + rand5()*2 + rand5()*3 + rand5()*4 + rand5()*5 + rand5()*6
  }
  return r %% 7 + 1
}

的分配rand5将保留。如果进行数学运算,则循环的7个迭代中的每一个都有5 ^ 6个可能的组合,因此,可能的组合的总数为(7 * 5^6) %% 7 = 0。因此,我们可以将生成的随机数划分为7个相等的组。有关此的更多讨论,请参见方法2。

以下是所有可能的组合:

table(apply(expand.grid(c(outer(1:5,0:6,"+")),(1:5)*2,(1:5)*3,(1:5)*4,(1:5)*5,(1:5)*6),1,sum) %% 7 + 1)

    1     2     3     4     5     6     7 
15625 15625 15625 15625 15625 15625 15625 

我认为直接表明亚当的方法将运行得快得多。rand5亚当的解决方案中有42个或更多调用的可能性非常小((4/25)^21 ~ 10^(-17))。

方法2-不精确

现在,第二种方法几乎是统一的,但需要6次调用rand5

rand7 <- function() (sum(sapply(1:6,function(i) i*rand5())) %% 7) + 1

这是一个简化的版本:

rand7 = function(){
  r = 0 
  for(i in 1:6){
    r = r + i*rand5()
  }
  return r %% 7 + 1
}

这本质上是方法1的一次迭代。如果我们生成所有可能的组合,则结果为:

table(apply(expand.grid(1:5,(1:5)*2,(1:5)*3,(1:5)*4,(1:5)*5,(1:5)*6),1,sum) %% 7 + 1)

   1    2    3    4    5    6    7 
2233 2232 2232 2232 2232 2232 2232

一个数字将在5^6 = 15625审判中再次出现。

现在,在方法1中,通过将1加到6,我们将数字2233移动到每个连续点。因此,组合的总数将匹配。之所以可行,是因为5 ^ 6 %% 7 = 1,然后我们做了7个适当的变化,所以(7 * 5 ^ 6 %% 7 = 0)。

方法3-精确

如果可以理解方法1和2的参数,则方法3紧随其后,并且仅需要7次调用rand5。在这一点上,我认为这是一个精确解决方案所需的最少呼叫次数。

这是一个R实现:

rand5 <- function() sample(1:5,1)

rand7 <- function()  (sum(sapply(1:7, function(i) i * rand5())) %% 7) + 1

对于不熟悉R的人,这里是简化版本:

rand7 = function(){
  r = 0 
  for(i in 1:7){
    r = r + i * rand5()
  }
  return r %% 7 + 1
}

的分配rand5将保留。如果我们做数学运算,则循环的7个迭代中的每一个都有5种可能的结果,因此,可能的组合总数为(7 * 5) %% 7 = 0。因此,我们可以将生成的随机数划分为7个相等的组。有关此的更多讨论,请参见方法一和方法二。

以下是所有可能的组合:

table(apply(expand.grid(0:6,(1:5)),1,sum) %% 7 + 1)

1 2 3 4 5 6 7  
5 5 5 5 5 5 5 

我认为可以直接证明亚当的方法仍将运行得更快。rand5亚当的解决方案中有7个或更多呼叫的可能性仍然很小((4/25)^3 ~ 0.004)。

方法4-不精确

这是第二种方法的微小变化。它几乎是统一的,但是需要7次调用rand5,这是方法2的另外一个:

rand7 <- function() (rand5() + sum(sapply(1:6,function(i) i*rand5())) %% 7) + 1

这是一个简化的版本:

rand7 = function(){
  r = 0 
  for(i in 1:6){
    r = r + i*rand5()
  }
  return (r+rand5()) %% 7 + 1
}

如果我们生成所有可能的组合,则结果为:

table(apply(expand.grid(1:5,(1:5)*2,(1:5)*3,(1:5)*4,(1:5)*5,(1:5)*6,1:5),1,sum) %% 7 + 1)

    1     2     3     4     5     6     7 
11160 11161 11161 11161 11161 11161 11160

5^7 = 78125试验中,两个数字将减少一次。对于大多数目的,我可以接受。


1
我不熟悉R,但是除非我误解了它们的工作原理,否则方法1是不准确的。它具有(5 ^ 6)^ 7 = 5 ^ 42可能的结果,而不是(5 ^ 6)* 7;5 ^ 42不能被7整除。同样,方法3也不正确。它有5 ^ 7个可能的结果,而不是5 * 7。(在方法3中的最后一个循环迭代与i=7也没有任何影响,因为加入7*rand5()r不改变的值rMOD 7)
亚当罗森菲尔德

2

您需要的功能是rand1_7(),我编写了rand1_5(),以便您可以对其进行测试和绘制。

import numpy
def rand1_5():
    return numpy.random.randint(5)+1

def rand1_7():
    q = 0
    for i in xrange(7):  q+= rand1_5()
    return q%7 + 1
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.