关于算法:将随机范围从1-5扩展到1-7

Expand a random range from 1–5 to 1–7

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

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

  • 这相当于亚当·罗森菲尔德的解决方案,但对一些读者来说可能更清楚一些。它假设rand5()是一个函数,它返回一个范围从1到5(包括1到5)的统计随机整数。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    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索引随机选择飞镖板上的一个位置,如果没有得到好的结果,我们就继续投掷飞镖。

    正如亚当所说,在最坏的情况下,这种情况可能永远不会发生,但从统计上看,最坏的情况永远不会发生。:)


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

    1
    2
    3
    4
    5
    6
    7
    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次循环,但永远循环的概率是无穷小的。


    除了我的第一个答案,我想再加一个答案。此答案试图最小化每次调用rand7()时对rand5()的调用次数,以最大限度地利用随机性。也就是说,如果您认为随机性是一种宝贵的资源,我们希望尽可能多地使用它,而不丢弃任何随机位。这个答案与伊万的答案中的逻辑也有一些相似之处。好的。

    随机变量的熵是一个定义明确的量。对于具有相同概率(均匀分布)的n个状态的随机变量,熵为log2n。因此,rand5()的熵约为2.32193位,rand7()的熵约为2.80735位。如果我们希望最大限度地利用随机性,我们需要使用来自每个调用rand5()的全部2.32193位熵,并将它们应用于生成每个调用rand7()所需的2.80735位熵。那么,基本限制是,每次调用rand7()时,我们不能做得比log(7)/log(5)=1.20906调用rand5()更好。好的。

    旁注:除非另有规定,否则本答案中的所有对数均以2为底。假设rand5()返回范围[0,4]内的数字,假设rand7()返回范围[0,6]内的数字。分别将范围调整为[1,5]和[1,7]是很简单的。好的。

    那我们该怎么做呢?我们生成了一个介于0和1之间的无限精确随机实数(暂时假设我们可以计算并存储这样一个无限精确的数字——稍后我们会解决这个问题)。我们可以通过在基数5中生成数字来生成这样一个数字:我们选择随机数0。a1a2a3…,其中每个数字Ai通过调用rand5()来选择。例如,如果我们的RNG选择ai=1 for all i,那么忽略这不是很随机的事实,这将对应于实数1/5+1/52+1/53+…=1/4(几何级数之和)。好的。

    好的,我们选择了一个介于0和1之间的随机实数。我现在声称这样一个随机数是均匀分布的。直观地说,这很容易理解,因为每个数字都是统一选取的,而且数字的精确性是无限的。然而,关于这一点的正式证明更为复杂,因为我们现在处理的是连续分布而不是离散分布,所以我们需要证明,我们的数字存在于区间的概率[ab等于该区间的长度,b - a。证据留给读者作为练习。好的。

    既然我们有一个从范围[0,1]中均匀选择的随机实数,我们需要将它转换为范围[0,6]中的一系列均匀随机数,以生成rand7()的输出。我们该怎么做?与我们刚才所做的相反——我们将它转换为以7为基数的无限精确的十进制,然后每个以7为基数的数字将对应于EDOCX1的一个输出(0)。好的。

    以前面的例子为例,如果我们的rand5()产生无穷多的1流,那么我们的随机实数将是1/4。将1/4转换为7进制,我们得到无穷小的0.15151515……,因此我们将生成输出1、5、1、5、1、5等。好的。

    好的,我们有一个主要的想法,但是我们还有两个问题:我们不能计算或存储一个无限精确的实数,那么我们如何处理它的有限部分呢?其次,我们如何将它转换为7进制呢?好的。

    我们可以将0到1之间的数字转换为以7为基数的方法如下:好的。

  • 乘以7
  • 结果的整数部分是下一个以7为基数的数字
  • 减去整数部分,只剩下小数部分
  • 转到第1步
  • 为了解决无限精度的问题,我们计算了一个局部结果,并且我们还存储了一个关于结果可能是什么的上界。也就是说,假设我们给rand5()打了两次电话,两次都打了1。到目前为止,我们生成的数字是0.11(以5为基数)。不管对rand5()产生的无限系列调用的其余部分是什么,我们生成的随机实数永远不会大于0.12:0.11≤0.11xyz…< 0.12。好的。

    所以,保持对当前数字的跟踪,以及它可能需要的最大值,我们将这两个数字都转换为7。如果他们同意第一个k位,那么我们就可以安全地输出下一个k位--不管基数5的无穷大流是什么,它们都不会影响基数7表示的下一个k位!好的。

    这就是算法——为了生成rand7()的下一个输出,我们只生成rand5()的数字,因为我们需要确保我们确定地知道从随机实数转换为基数7的下一个数字的值。下面是一个python实现,带有一个测试工具:好的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    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()打了12091个电话,给rand7()打了10000个电话,平均达到了log(7)/log(5)的最低要求,达到了4个有效数字,结果是一致的。好的。

    为了将此代码移植到一种没有内置任意大整数的语言中,您必须将pow5pow7的值限制为本机整型的最大值——如果它们太大,则重置所有内容并重新开始。这将略微增加每次调用rand7()时对rand5()的平均调用数,但希望对于32位或64位整数来说,它不会增加太多。好的。好啊。


    (我偷了亚当·罗森菲尔德的答案,让它跑得快7%左右。)

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    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到最大值%7-1之间的随机数,再调用rand()生成一个新的数字和一个新的最大值,然后重新开始。

    编辑:期望调用rand5()的次数在此公式中为x:

    1
    2
    3
    4
    5
    6
    7
    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()


    算法:

    7可以用3位序列表示。

    使用rand(5)将每个位随机填充为0或1。例如:致电兰德公司(5)和

    如果结果为1或2,则用0填充位。如果结果为4或5,则用1填充位如果结果为3,则忽略并再次执行(拒绝)

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

    编辑:这似乎是最简单和最有效的答案,下面是一些代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    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
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    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 );
        }
    }


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

    编辑:那不太管用。每1000人中约有2分(假设是完美的Rand5)。这些桶得到:

    1
    2
    3
    4
    5
    6
    7
    8
    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

    通过切换到

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

    似乎每增加2个就增加一个数量级

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

    p[x,n] is the number ways output=x can happen given n calls to rand5.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
      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]


    1
    2
    3
    4
    5
    6
    7
    8
    9
    int ans = 0;
    while (ans == 0)
    {
         for (int i=0; i<3; i++)
         {
              while ((r = rand5()) == 3){};
              ans += (r < 3) >> i
         }
    }


    下面使用随机数生成器在1、2、3、4、5、6、7上生成均匀分布,在1、2、3、4、5上生成均匀分布。代码混乱,但逻辑清晰。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    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;
    }


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

    分析这一点的最简单方法是分别将流i和O视为5元数和7元数。这是通过主要答案的想法来实现的,即采用a1, a2, a3,... -> a1+5*a2+5^2*a3+..流,类似地,采用O流。

    然后,如果我们取一段长度为m choose n s.t. 5^m-7^n=c的输入流,其中c>0尽可能小。然后有一个从长度m的输入流到从15^m的整数的统一映射,还有一个从1到7^n的整数到长度n的输出流的统一映射,当映射的整数超过7^n时,我们可能需要从输入流中丢失一些情况。

    因此,这给了L(m)的值,约为m (log5/log7),约为.82m

    上述分析的难点是方程5^m-7^n=c,不容易精确求解,且15^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)是从大小为X的统一输入派生的random(1-7)整数的输出序列的平均长度,并且假设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),因为我们有一个长度没有序列,概率为7^n0/5^m,剩余长度为5^m-7^n0,概率为(5^m-7^n0)/5^m)

    如果我们继续替换,我们得到:

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

    因此

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

    另一种说法是:

    1
    2
    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

    然后像以前一样,以东十一〔35〕。

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

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

    其他案件在中间。很有意思的是,我们能为非常大的m做得多好,也就是说,我们能得到多好的错误项:

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

    一般来说,实现e(m) = o(1)似乎是不可能的,但希望我们能够证明e(m)=o(m)

    然后,整个问题取决于5^m的7位数字对m的各种值的分布。

    我相信有很多理论可以涵盖这一点,我可能会在某个时候回顾和报告。


    这里允许作业问题吗?

    这个函数执行粗略的"基数5"数学运算,生成一个介于0和6之间的数字。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    function rnd7() {
        do {
            r1 = rnd5() - 1;
            do {
                r2=rnd5() - 1;
            } while (r2 > 1);
            result = r2 * 5 + r1;
        } while (result > 6);
        return result + 1;
    }


    为什么不简单一点?

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

    在这个解决方案中得到1和7的机会要小一些,这是由于使用了模块,但是,如果您只想得到一个快速、易读的解决方案,这是一种可行的方法。


    下面是亚当答案的一个有效的Python实现。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    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中,这样我就可以和它们一起玩了,我想我会把它放在这里,希望它对外面的人有用,而不是花很长时间把它们放在一起。


    我的答案是:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    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的调用。与其他解决方案一样,它可能会循环很长一段时间。


    亚当·罗森菲尔德正确答案的前提是:

    • 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
    7*2232=15624

    你再给Rand5打电话。但是,获得丢弃值(或无限循环)的机会要小得多。如果有一种方法不可能得到Y的丢弃值,我还没有找到它。


    假设rand(n)这里的意思是"从0到n-1的均匀分布中的随机整数",这里有一个使用python的randint的代码示例,它具有这种效果。它只使用randint(5)和常量来产生randint(7)的效果。其实有点傻

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    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
    2
    3
    4
    5
    int rand7 ( void )
    {
        return 4; // this number has been calculated using
                  // rand5() and is in the range 1..7
    }

    (灵感来源于你最喜欢的"程序员"动画片?).


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

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

    请注意,这个生成器并不完美(数字0的概率比任何其他数字都高0.0064%),但对于大多数实际用途来说,恒定时间的保证可能超过了这种不准确。

    解释

    这个解是由数字15624可被7整除的事实得出的,因此如果我们能随机均匀地生成0到15624之间的数字,然后取mod 7,我们就可以得到一个近似均匀的rand7生成器。从0到15624的数字可以通过滚动rand5 6次并使用它们形成以5为基数的数字,如下所示:

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

    但是,mod 7的特性允许我们稍微简化方程式:

    1
    2
    3
    4
    5
    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

    所以

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

    变成

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

    理论

    数字15624不是随机选择的,但是可以用费马的小定理来发现,它指出如果p是素数,那么

    1
    a^(p-1) = 1 mod p

    所以这给了我们,

    1
    (5^6)-1 = 0 mod 7

    (5^6)-1等于

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

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


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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    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;
    }


    只要没有七种可能性可供选择,再画一个随机数,将可能性数乘以五。在Perl:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    $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;
    }


    我知道已经有人回答了,但这是否可行,但我不能告诉你是否有偏见。我的"测试"表明这至少是合理的。

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

    我的(幼稚?)想法是这样的:

    积累rand5直到有足够的随机位生成rand7。这最多需要2个Rand5。为了得到Rand7数字,我使用累积值mod 7。

    为了避免蓄能器溢出,由于蓄能器是7型,所以我取蓄能器的7型:

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

    rand7()函数如下:

    (我让rand5的范围是0-4,rand7的范围也是0-6。)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    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:平均值=1.999802 0:20003944 1:19999889 2:20003690 3:19996938 4:19995539Rand7:平均值=3.000111 0:14282851 1:14282879 2:14284554 3:14288546 4:14292388 5:14288736 6:14280046

    我的兰德7

    平均值看起来不错,数字分布也不错。

    平均值=3.000080 0:14288793 1:14280135 2:14287848 3:14285277 4:14286341 5:14278663 6:14292943


    给你,统一分配,零随机5个电话。

    1
    2
    3
    4
    5
    def rand7:
        seed += 1
        if seed >= 7:
            seed = 0
        yield seed

    需要预先播种。


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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    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),则会得到:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    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

    分布良好且平坦(在每箱10^8的1/7的约10 k范围内,正如从近似高斯分布中预期的那样)。


    上面引用了一些优雅的算法,但这里有一种方法可以接近它,尽管它可能是迂回的。我假设从0生成的值。

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

    为了从r2生成r8,您将运行r2三次,并使用所有3次运行的组合结果作为3位二进制数。以下是r2运行三次时的值范围:

    0 0 0 0>0..1 1 1 1>7

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

    1
    2
    3
    4
    5
    6
    int R7() {
      do {
        x = R8();
      } while (x > 6)
      return x;
    }

    迂回方案是从R5生成R2(就像我们从R8生成R7一样),然后从R2生成R8,然后从R8生成R7。


    这个答案更像是从rand5函数中获得最大熵的实验。因此,T有点不清楚,几乎肯定比其他实现慢得多。

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    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的比为2.125次呼叫到rand5。最佳值应为2.1。

    我可以想象,这种方法要比其他方法慢得多,除非调用rand5的成本非常昂贵(比如调用外部熵源)。


    通过使用滚动总数,可以同时使用

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

    这两个问题都与简化的rand(5)+rand(5)...型解决方案有关。下面的python代码展示了如何实现它(其中大部分是证明发行版)。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    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)

    结果如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    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倍:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    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%)

    而且,根据尼克苏兹的建议,我已经清理了脚本,这样您就可以提取和使用rand7...的内容:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    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)


    在PHP中

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    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个结果中的任何一个。


    1
    2
    3
    4
    5
    extern int r5();

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


    您需要的函数是rand1_7(),我编写了rand1_5(),这样您就可以测试它并绘制它了。

    1
    2
    3
    4
    5
    6
    7
    8
    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

    为什么这个不行?另一个是对rand5()的额外调用?

    1
    2
    3
    i = rand5() + rand5() + (rand5() - 1) //Random number between 1 and 14

    i = i % 7 + 1;


    1
    2
    3
    4
    5
    6
    7
    function Rand7
       put 200 into x
       repeat while x > 118
          put ((random(5)-1) * 25) + ((random(5)-1) * 5) + (random(5)-1) into x
       end repeat
       return (x mod 7) + 1
    end Rand7

    三次呼叫Rand5,平均每125次只重复6次。

    把它想象成一个3d数组,5x5x5,一次又一次地填充1到7个空格,还有6个空格。在毛坯上再滚一遍。rand5调用在该数组中创建一个三位数的base-5索引。

    4D或更高的N维数组的重复次数会更少,但这意味着更多对rand5函数的调用成为标准。你将开始在更高维度上获得递减的效率回报。在我看来,三个似乎是一个很好的折衷办法,但我还没有确定地测试过他们之间的对立。它将是特定于Rand5实现的。


    1
    2
    3
    4
    5
    6
    7
    int getOneToSeven(){
        int added = 0;
        for(int i = 1; i<=7; i++){
            added += getOneToFive();
        }
        return (added)%7+1;
    }


    这是我在回顾其他人的答案后可以创建的最简单的答案:

    1
    2
    3
    4
    5
    def r5tor7():
        while True:
            cand = (5 * r5()) + r5()
            if cand < 27:
                return cand

    cand在[6,27]范围内,如果r5()的可能结果均匀分布,则可能的结果均匀分布。您可以用以下代码测试我的答案:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    from collections import defaultdict

    def r5_outcome(n):
        if not n:
            yield []
        else:
            for i in range(1, 6):
                for j in r5_outcome(n-1):
                    yield [i] + j

    def test_r7():
        d = defaultdict(int)
        for x in r5_outcome(2):
            s = sum([x[i] * 5**i for i in range(len(x))])
            if s < 27:
                d[s] += 1
        print len(d), d

    r5_outcome(2)生成R5()结果的所有可能组合。我使用与解决方案代码中相同的过滤器进行测试。你可以看到所有的结果都是一样的,可能是因为它们具有相同的价值。


    只需从第一个函数缩放输出

    1
    2
    3
    4
    0) you have a number in range 1-5
    1) subtract 1 to make it in range 0-4
    2) multiply by (7-1)/(5-1) to make it in range 0-6
    3) add 1 to increment the range: Now your result is in between 1-7


    1
    2
    3
    4
    5
    6
    7
    8
    rand25() =5*(rand5()-1) + rand5()

    rand7() {
       while(true) {
           int r = rand25();
           if (r < 21) return r%3;        
       }
    }

    为什么这样做:循环将永远运行的概率是0。


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    package CareerCup;

    public class RangeTransform {
     static int counter = (int)(Math.random() * 5 + 1);

     private int func() {
      return (int) (Math.random() * 5 + 1);
     }

     private int getMultiplier() {
      return counter % 5 + 1;
     }

     public int rangeTransform() {
      counter++;
      int count = getMultiplier();
      int mult = func() + 5 * count;
      System.out.println("Mult is :" + 5 * count);
      return (mult) % 7 + 1;
     }

     /**
      * @param args
      */
     public static void main(String[] args) {
      // TODO Auto-generated method stub
      RangeTransform rangeTransform = new RangeTransform();
      for (int i = 0; i < 35; i++)
       System.out.println("Val is :" + rangeTransform.rangeTransform());
     }
    }

    假设rand对所有位给予相等的权重,然后用上界屏蔽。

    1
    int i = rand(5) ^ (rand(5) & 2);

    rand(5)只能返回:1b10b11b100b101b。你只需要关心自己有时设置2位。


    以下是我发现的:

  • random5产生1~5的范围,随机分布
  • 如果我们运行3次,把它们加在一起,我们会得到3~15的范围,随机分布
  • 在3~15范围内进行算术运算
  • (3~15)-1=(2~14)
  • (2~14)/2=(1~7)
  • 然后我们得到1~7的范围,这就是我们要找的随机数。


    对于值0-7,您有以下内容:

    1
    2
    3
    4
    5
    6
    7
    8
    0 000
    1 001
    2 010
    3 011
    4 100
    5 101
    6 110
    7 111

    从左到右按位排列,rand5()有p(1)=2/5、2/5、3/5。因此,如果我们对这些概率分布(~rand5())进行补充,我们应该能够使用它来产生我们的数字。稍后我将尝试用解决方案报告。有人有什么想法吗?

    R


    我们这里使用的是惯例rand(n) -> [0, n - 1]

    从我读到的许多答案来看,它们要么提供一致性保证,要么提供暂停保证,但不能同时提供两者(亚当·罗森菲尔德的第二个答案可能)。

    然而,这样做是可能的。我们基本上有这样的分布:

    rand5_proba.png

    这给我们在[0-6]上的分布留下了一个漏洞:5和6没有发生概率。想象一下,现在我们试图通过移动概率分布和求和。

    实际上,我们可以将初始分布本身移动一个,并且通过将获得的分布与初始分布相加重复2,然后3等等,直到7,不包括在内(我们覆盖了整个范围)。如下图所示。颜色的顺序,对应于这些步骤是蓝色、绿色、青色、白色、洋红、黄色、红色。

    fig_moving_average_proba.png

    因为每个槽被7个移位分布中的5个覆盖(移位随0到6),因为我们假设随机数与1无关ran5()呼叫另一个,我们得到

    1
    p(x) = 5 / 35 = 1 / 7       for all x in [0, 6]

    这意味着,给定来自ran5()的7个独立随机数,我们可以计算一个在[0-6]范围内概率均匀的随机数。实际上,ran5()概率只要样品是均匀的,分布就不需要均匀。独立的(所以从试验到试验的分布是一样的)。此外,这对5和7以外的其他数字有效。

    这为我们提供了以下python函数:

    1
    2
    3
    4
    5
    6
    def rand_range_transform(rands):
       """
        returns a uniform random number in [0, len(rands) - 1]
        if all r in rands are independent random numbers from the same uniform distribution
       """
        return sum((x + i) for i, x in enumerate(rands)) % len(rands) # a single modulo outside the sum is enough in modulo arithmetic

    可以这样使用:

    1
    2
    3
    4
    rand5 = lambda : random.randrange(5)

    def rand7():
        return rand_range_transform([rand5() for _ in range(7)])

    如果我们给rand7()70000打电话,我们可以得到:

    1
    2
    3
    4
    5
    6
    7
    8
    max: 6 min: 0 mean: 2.99711428571 std: 2.00194697049
    0:  10019
    1:  10016
    2:  10071
    3:  10044
    4:  9775
    5:  10042
    6:  10033

    这很好,虽然还远远不够完美。事实上,我们的假设之一是在这个实现中很可能是错误的:我们使用一个prng,因此,结果下一个调用的依赖于上一个结果。

    也就是说,使用真正随机的数字源,输出也应该真随机。这种算法在任何情况下都会终止。

    但这是有代价的:我们需要7个电话给rand5(),一个rand7()打电话。


    这个解决方案不浪费任何熵,并给出了范围内第一个真正可用的随机数。每一次迭代都会减少得不到答案的概率。在n次迭代中得到答案的概率是0到max(5^n)之间的随机数将小于该范围内7的最大倍数(max%7)的概率。必须至少重复两次。但这对于所有的解决方案都是必然的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    int random7() {
      range = 1;
      remainder = 0;

      while (1) {
        remainder = remainder * 5 + random5() - 1;
        range = range * 5;

        limit = range - (range % 7);
        if (remainder < limit) return (remainder % 7) + 1;

        remainder = remainder % 7;
        range = range % 7;
      }
    }

    数值上等同于:

    1
    2
    3
    4
    5
    6
    7
    8
    r5=5;
    num=random5()-1;
    while (1) {
       num=num*5+random5()-1;
       r5=r5*5;
       r7=r5-r5%7;
       if (num<r7) return num%7+1;
    }

    第一个代码以模块形式计算它。第二个代码只是简单的数学。或者我在某个地方犯了个错误。-)


    给定一个在1到5个rand5()范围内产生随机整数的函数,写出一个在1到7个rand7()范围内产生随机整数的函数。

    在我提出的解决方案中,我只打一次电话给rand5

    实解

    1
    2
    3
    4
    float rand7()
    {
        return (rand5() * 7.0) / 5.0 ;
    }

    这里的分布是按比例分布的,所以它直接取决于rand5的分布。

    整数解

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    int rand7()
    {
        static int prev = 1;

        int cur = rand5();

        int r = cur * prev; // 1-25

        float f = r / 4.0; // 0.25-6.25

        f = f - 0.25; // 0-6

        f = f + 1.0; // 1-7

        prev = cur;

        return (int)f;
    }

    这里的分布取决于系列rand7(i) ~ rand5(i) * rand5(i-1)

    rand7(0) ~ rand5(0) * 1一起


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

    最好的精确解决方案需要7次调用rand5,但是让我们继续了解。

    方法1-精确

    Adam的答案的优势在于它提供了一个完美的统一分布,并且非常有可能(21/25)只需要两个对rand5()的调用。然而,最坏的情况是无限循环。

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

    下面是一个R实现:

    1
    2
    3
    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的人,这里有一个简化版本:

    1
    2
    3
    4
    5
    6
    7
    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组。关于这一点的更多讨论见方法二。

    以下是所有可能的组合:

    1
    2
    3
    4
    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

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

    方法2-不准确

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

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

    下面是一个简化版本:

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

    这基本上是方法1的一次迭代。如果我们生成所有可能的组合,下面是结果计数:

    1
    2
    3
    4
    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实现:

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

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

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

    1
    2
    3
    4
    5
    6
    7
    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组。关于这一点的更多讨论见方法一和方法二。

    以下是所有可能的组合:

    1
    2
    3
    4
    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

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

    方法4-不准确

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

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

    下面是一个简化版本:

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

    如果我们生成所有可能的组合,下面是结果计数:

    1
    2
    3
    4
    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
    2
    3
    4
    5
    6
    int rand7() {
      int r = 7 / 2;
      for (int i = 0; i < 28; i++)
        r = ((rand5() - 1) * 7 + r) / 5;
      return r + 1;
    }

    在每次迭代中,r是一个介于0和6之间的随机值。这是在0到4之间(含0和4)的随机值后加上(以7为底),结果除以5,得到0到6之间(含0和6)的新随机值。r开始时有很大的偏向(r = 3非常偏向!)但是每一次迭代都会将这个偏差除以5。

    这种方法并不完全一致;但是,偏差非常小。按1/(2**64)的顺序排列的东西。这种方法的重要之处在于它有恒定的执行时间(假设rand5()也有恒定的执行时间)。没有理论上的担忧,一个不幸的调用可能永远重复选择坏值。

    另外,一个对良好措施的讽刺性回答(不管是有意还是无意,它已经被涵盖):

    1-5已经在1-7范围内,因此以下是有效的实现:

    1
    2
    3
    int rand7() {
      return rand5();
    }

    这个问题没有要求统一分配。


    下面是一个利用C++ 11中的特性的答案

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    #include <functional>
    #include <iostream>
    #include <ostream>
    #include <random>

    int main()
    {
        std::random_device rd;
        unsigned long seed = rd();
        std::cout <<"seed =" << seed << std::endl;

        std::mt19937 engine(seed);

        std::uniform_int_distribution<> dist(1, 5);
        auto rand5 = std::bind(dist, engine);

        const int n = 20;
        for (int i = 0; i != n; ++i)
        {
            std::cout << rand5() <<"";
        }
        std::cout << std::endl;

        // Use a lambda expression to define rand7
        auto rand7 = [&rand5]()->int
        {
            for (int result = 0; ; result = 0)
            {
                // Take advantage of the fact that
                // 5**6 = 15625 = 15624 + 1 = 7 * (2232) + 1.
                // So we only have to discard one out of every 15625 numbers generated.

                // Generate a 6-digit number in base 5
                for (int i = 0; i != 6; ++i)
                {
                    result = 5 * result + (rand5() - 1);
                }

                // result is in the range [0, 15625)
                if (result == 15625 - 1)
                {
                    // Discard this number
                    continue;
                }

                // We now know that result is in the range [0, 15624), a range that can
                // be divided evenly into 7 buckets guaranteeing uniformity
                result /= 2232;
                return 1 + result;
            }
        };

        for (int i = 0; i != n; ++i)
        {
            std::cout << rand7() <<"";
        }
        std::cout << std::endl;

        return 0;
    }

    我觉得你们都想得太多了。这个简单的解决方案不管用吗?

    1
    2
    3
    4
    5
    6
    int rand7(void)
    {
        static int startpos = 0;
        startpos = (startpos+5) % (5*7);
        return (((startpos + rand5()-1)%7)+1);
    }

    这个解决方案的灵感来自Rob McAfee。但是,它不需要循环,结果是均匀分布:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    // Returns 1-5
    var rnd5 = function(){
       return parseInt(Math.random() * 5, 10) + 1;
    }
    // Helper
    var lastEdge = 0;
    // Returns 1-7
    var rnd7 = function () {
      var map = [
         [ 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 ]
      ];
      var result = map[rnd5() - 1][rnd5() - 1];
      if (result > 0) {
        return result;
      }
      lastEdge++;
      if (lastEdge > 7 ) {
        lastEdge = 1;
      }
      return lastEdge;
    };

    // Test the a uniform distribution
    results = {}; for(i=0; i < 700000;i++) { var rand = rnd7(); results[rand] = results[rand] ? results[rand] + 1 : 1;}
    console.log(results)

    结果:[1: 99560, 2: 99932, 3: 100355, 4: 100262, 5: 99603, 6: 100062, 7: 100226]

    jsfiddle(jsfiddle)


    这与@robmcafee类似,只是我使用的是幻数而不是二维数组。

    1
    2
    3
    4
    5
    6
    int rand7() {
        int m = 1203068;
        int r = (m >> (rand5() - 1) * 5 + rand5() - 1) & 7;

        return (r > 0) ? r : rand7();
    }

    1
    2
    3
    4
    5
    6
    7
    function rand7() {
        while (true) { //lowest base 5 random number > 7 reduces memory
            int num = (rand5()-1)*5 + rand5()-1;
        if (num < 21)  // improves performance
            return 1 + num%7;
        }
    }

    Python代码:

    1
    2
    3
    4
    5
    6
    from random import randint
    def rand7():
        while(True):
            num = (randint(1, 5)-1)*5 + randint(1, 5)-1
            if num < 21:
                    return 1 + num%7

    100000次运行的试验分布:

    1
    2
    3
    4
    5
    >>> rnums = []
    >>> for _ in range(100000):
        rnums.append(rand7())
    >>> {n:rnums.count(n) for n in set(rnums)}
    {1: 15648, 2: 15741, 3: 15681, 4: 15847, 5: 15642, 6: 15806, 7: 15635}

    这里有很多不产生统一分布的解决方案,许多评论指出了这一点,但问题并没有将其作为一项要求。最简单的解决方案是:

    1
    int rand_7() { return rand_5(); }

    1-5范围内的随机整数显然在1-7范围内。从技术上讲,最简单的解决方案是返回一个常数,但这太微不足道了。

    但是,我认为rand_5函数的存在是一个红鲱鱼。假设问题是"生成一个均匀分布的伪随机数生成器,其整数输出在1-7范围内"。这是一个简单的问题(技术上不简单,但已经解决了,所以您可以查找它。)

    另一方面,如果问题被解释为意味着您实际上有一个真正的随机数生成器,用于1-5范围内的整数(而不是伪随机数),那么解决方案是:

    1
    2
    3
    1) examine the rand_5 function
    2) understand how it works
    3) profit

    这是我的一般实现,在[0,n-1]范围内生成一个统一的,给定范围[0,b-1]内的一个统一的生成器。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    public class RandomUnif {

        public static final int BASE_NUMBER = 5;

        private static Random rand = new Random();

        /** given generator, returns uniform integer in the range 0.. BASE_NUMBER-1
        public static int randomBASE() {
            return rand.nextInt(BASE_NUMBER);
        }

        /** returns uniform integer in the range 0..n-1 using randomBASE() */
        public static int randomUnif(int n) {
            int rand, factor;
            if( n <= 1 ) return 0;
            else if( n == BASE_NUMBER ) return randomBASE();
            if( n < BASE_NUMBER ) {
                factor = BASE_NUMBER / n;
                do
                    rand = randomBASE() / factor;
                while(rand >= n);
                return rand;
            } else {
                factor = (n - 1) / BASE_NUMBER + 1;
                do {
                    rand = factor * randomBASE() + randomUnif(factor);
                } while(rand >= n);
                return rand;
            }
        }
    }

    不是惊人的效率,而是一般和紧凑。对基本生成器的平均调用:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
     n  calls
     2  1.250
     3  1.644
     4  1.252
     5  1.000
     6  3.763
     7  3.185
     8  2.821
     9  2.495
    10  2.250
    11  3.646
    12  3.316
    13  3.060
    14  2.853
    15  2.650
    16  2.814
    17  2.644
    18  2.502
    19  2.361
    20  2.248
    21  2.382
    22  2.277
    23  2.175
    24  2.082
    25  2.000
    26  5.472
    27  5.280
    28  5.119
    29  4.899

    1
    2
    3
    4
    int rand7()
    {
        return ( rand5() + (rand5()%3) );
    }
  • rand5()-返回1-5之间的值
  • rand5()%3-返回0-2之间的值
  • 所以,当求和时,总值将在1-7之间

  • 我想到的第一件事就是这个。但我不知道它是否均匀分布。在python中实现

    import random

    def rand5():

    return random.randint(1,5)

    DEF RAND7():

    return ( ( (rand5() -1) * rand5() ) %7 )+1

    < /块引用>


    从一个扩大浮动范围的链环来到这里。这个更有趣。我没有得出这样的结论,而是想到,对于一个给定的随机整数生成函数f和"base"b(在本例中是4),它可以展开如下:

    1
    (b^0 * f() + b^1 * f() + b^2 * f() .... b^p * f()) / (b^(p+1) - 1) * (b-1)

    这将把随机生成器转换为浮点生成器。我将在这里定义两个参数:bp。虽然这里的"基数"是4,但实际上b可以是任何东西,它也可以是无理数等。p,我称精度是您希望浮点生成器具有多大的粒度。把这看作是对rand7的每个调用对rand5所做的调用数。

    但是我意识到如果你把b设置为base+1(在这个例子中是4+1=5),这是一个最佳位置,你会得到一个均匀的分布。首先去掉这个1-5生成器,它实际上是rand4()+1:

    1
    2
    3
    function rand4(){
        return Math.random() * 5 | 0;
    }

    你可以用rand5()-1代替rand4

    接下来是将rand4从整数生成器转换为浮点生成器

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    function toFloat(f,b,p){
        b = b || 2;
        p = p || 3;
        return (Array.apply(null,Array(p))
        .map(function(d,i){return f()})
        .map(function(d,i){return Math.pow(b,i)*d})
        .reduce(function(ac,d,i){return ac += d;}))
        /
        (
            (Math.pow(b,p) - 1)
            /(b-1)
        )
    }

    这将应用我编写的第一个函数到给定的rand函数。试试看:

    1
    2
    3
    4
    5
    toFloat(rand4) //1.4285714285714286 base = 2, precision = 3
    toFloat(rand4,3,4) //0.75 base = 3, precision = 4
    toFloat(rand4,4,5) //3.7507331378299122 base = 4, precision = 5
    toFloat(rand4,5,6) //0.2012288786482335 base = 5, precision =6
    ...

    现在您可以将这个浮动范围(包括0-4)转换为任何其他浮动范围,然后将其降级为整数。在这里,我们的基础是4,因为我们处理的是rand4,因此值b=5将给您一个统一的分布。当b超过4时,您将开始在分布中引入周期性间隙。我测试了2到8之间的B值,每个值为3000点,与原生数学相比。随机的javascript在我看来比原生的还要好:

    http://jsfiddle.net/ibowankenobi/r57v432t/

    对于上面的链接,单击分布顶部的"bin"按钮以减小binning大小。最后一个图是原生的math.random,第四个图中d=5是均匀的。

    得到浮点数范围后,要么乘以7,再加上小数部分,要么乘以7,再减去0.5,然后四舍五入:

    1
    2
    ((toFloat(rand4,5,6)/4 * 7) | 0) + 1   ---> occasionally you'll get 8 with 1/4^6 probability.
    Math.round((toFloat(rand4,5,6)/4 * 7) - 0.5) + 1 --> between 1 and 7


    我想到了一个有趣的解决这个问题的方法,想和大家分享一下。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    function rand7() {

        var returnVal = 4;

        for (var n=0; n<3; n++) {
            var rand = rand5();

            if (rand==1||rand==2){
                returnVal+=1;
            }
            else if (rand==3||rand==4) {
                returnVal-=1;
            }
        }

        return returnVal;
    }

    我构建了一个测试函数,它循环使用rand7()10000次,汇总所有返回值,并将其除以10000。如果rand7()工作正常,我们计算的平均值应该是4-例如,(1+2+3+4+5+6+7/7)=4。在做了多次测试后,平均值实际上是4:)


    这个怎么样?

    rand5()%2+rand5()%2+rand5()%2+rand5()%2+rand5()%2+rand5()%2

    不确定这是均匀分布的。有什么建议吗?


  • 什么是简单的解决方案?(rand5() + rand5()) % 7 + 1
  • 什么是减少内存使用或在较慢的CPU上运行的有效解决方案?Yes, this is effective as it calls rand5() only twice and have O(1) space complexity
  • 考虑到rand5()给出了从1到5(含)的随机数。(1+1)%7+1=3(1+2)%7+1=4(1+3)%7+1=5(1+4)%7+1=6(1+5)%7+1=7

    (2+1)%7+1=4(2+2)%7+1=5(2+3)%7+1=6(2+4)%7+1=7(2+5)%7+1=1…

    (5+1)%7+1=7(5+2)%7+1=1(5+3)%7+1=2(5+4)%7+1=3(5+5)%7+1=4…

    等等


    与马丁的答案相似,但倾向于减少熵的浪费:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    int rand7(void) {
      static int m = 1;
      static int r = 0;

      for (;;) {
        while (m <= INT_MAX / 5) {
          r = r + m * (rand5() - 1);
          m = m * 5;
        }
        int q = m / 7;
        if (r < q * 7) {
          int i = r % 7;
          r = r / 7;
          m = q;
          return i + 1;
        }
        r = r - q * 7;
        m = m - q * 7;
      }
    }

    在这里,我们在0m-1之间建立一个随机值,并尝试通过添加尽可能多的状态来最大化m,而不产生溢出(INT_MAX是在c中适合int的最大值,或者您可以用您的语言和体系结构中有意义的任何大值来替换它)。

    那么,如果r落在最大可能区间内,平均可被7除,那么它就包含一个可行的结果,我们可以将该区间除以7,并将剩余的部分作为结果,然后将剩余的值返回到熵池中。否则,r处于另一个不均匀划分的区间中,我们必须从这个不合适的区间中丢弃并重新启动我们的熵池。

    与这里流行的答案相比,它称rand5()大约是平均频率的一半。

    为了提高性能,可以将这些划分分解为琐碎的位旋转和LUT。


    简单的解决方案已经被很好地覆盖了:取两个random5样本作为一个random7结果,如果结果超出了产生均匀分布的范围,就重新进行。如果您的目标是减少对random5的调用次数,这是非常浪费的-由于丢弃的样本数量,每个random7输出对random5的平均调用次数为2.38,而不是2。

    您可以通过使用更多的random5输入一次生成多个random7输出来做得更好。对于用31位整数计算的结果,当使用12个对random5的调用生成9个random7输出时,最佳值出现,平均每个输出调用1.34个。它的效率很高,因为244140625个结果中只有2018983个需要废弃,或者少于1%。

    Python中的演示

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    def random5():
        return random.randint(1, 5)

    def random7gen(n):
        count = 0
        while n > 0:
            samples = 6 * 7**9
            while samples >= 6 * 7**9:
                samples = 0
                for i in range(12):
                    samples = samples * 5 + random5() - 1
                    count += 1
            samples //= 6
            for outputs in range(9):
                yield samples % 7 + 1, count
                samples //= 7
                count = 0
                n -= 1
                if n == 0: break

    >>> from collections import Counter
    >>> Counter(x for x,i in random7gen(10000000))
    Counter({2: 1430293, 4: 1429298, 1: 1428832, 7: 1428571, 3: 1428204, 5: 1428134, 6: 1426668})
    >>> sum(i for x,i in random7gen(10000000)) / 10000000.0
    1.344606

    该算法将rand5的调用次数减少到理论上的最小值7/5。通过产生下5个rand7数字7次调用它。

    不存在任何随机位的拒绝,也不可能一直等待结果。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    #!/usr/bin/env ruby

    # random integer from 1 to 5
    def rand5
        STDERR.putc '.'
        1 + rand( 5 )
    end

    @bucket = 0
    @bucket_size = 0

    # random integer from 1 to 7
    def rand7
        if @bucket_size == 0
            @bucket = 7.times.collect{ |d| rand5 * 5**d }.reduce( &:+ )
            @bucket_size = 5
        end

        next_rand7 = @bucket%7 + 1

        @bucket      /= 7
        @bucket_size -= 1

        return next_rand7
    end

    35.times.each{ putc rand7.to_s }

    这里是我的,这试图从多个rand5()函数调用中重新创建Math.random(),通过用"加权分数"(?)重新构造来重建一个单位间隔(Math.random()的输出范围)。。然后使用此随机单位间隔生成介于1和7之间的随机整数:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    function rand5(){
      return Math.floor(Math.random()*5)+1;
    }
    function rand7(){
      var uiRandom=0;
      var div=1;
      for(var i=0; i<7; i++){
        div*=5;
        var term=(rand5()-1)/div;
        uiRandom+=term;
      }
      //return uiRandom;
      return Math.floor(uiRandom*7)+1;
    }

    释义:我们取一个0-4之间的随机整数(只有rand5()-1)并将每个结果乘以1/5、1/25、1/125,…。然后把它们加起来。它类似于二元加权分数的工作方式;我想,我们将它称为五元(基-5)加权分数:从0到0.999999生成一个数,作为一系列(1/5)^n项。

    修改函数以获取任何输入/输出随机整数范围应该很简单。当重写为一个闭包时,可以优化上面的代码。

    或者,我们也可以这样做:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    function rand5(){
      return Math.floor(Math.random()*5)+1;
    }
    function rand7(){
      var buffer=[];
      var div=1;
      for (var i=0; i<7; i++){
        buffer.push((rand5()-1).toString(5));
        div*=5;
      }
      var n=parseInt(buffer.join(""),5);
      var uiRandom=n/div;
      //return uiRandom;
      return Math.floor(uiRandom*7)+1;
    }

    我们不必费心去构造一个五元(基-5)加权分数,我们实际上会得到一个五元数,然后把它变成一个分数(0-0.9999…如前所述),然后从那里计算我们的随机1-7位数。

    以上结果(代码段2:3次运行,每次调用100000次):

    1: 14263; 2: 14414; 3: 14249; 4: 14109; 5: 14217; 6: 14361; 7: 14387

    1: 14205; 2: 14394; 3: 14238; 4: 14187; 5: 14384; 6: 14224; 7: 14368

    1: 14425; 2: 14236; 3: 14334; 4: 14232; 5: 14160; 6: 14320; 7: 14293


    首先,我在1点上移动ramdom5()6次,得到7个随机数。第二,我加上7个数,得到一个和。第三,我在7点得到了除法的其余部分。最后,我加1得到从1到7的结果。该方法给出了从1到7范围内获得数字的相等概率,1除外。1的概率稍高。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    public int random7(){
        Random random = new Random();
        //function (1 + random.nextInt(5)) is given
        int random1_5 = 1 + random.nextInt(5); // 1,2,3,4,5
        int random2_6 = 2 + random.nextInt(5); // 2,3,4,5,6
        int random3_7 = 3 + random.nextInt(5); // 3,4,5,6,7
        int random4_8 = 4 + random.nextInt(5); // 4,5,6,7,8
        int random5_9 = 5 + random.nextInt(5); // 5,6,7,8,9
        int random6_10 = 6 + random.nextInt(5); //6,7,8,9,10
        int random7_11 = 7 + random.nextInt(5); //7,8,9,10,11

        //sumOfRandoms is between 28 and 56
        int sumOfRandoms = random1_5 + random2_6 + random3_7 +
                           random4_8 + random5_9 + random6_10 + random7_11;
        //result is number between 0 and 6, and
        //equals 0 if sumOfRandoms = 28 or 35 or 42 or 49 or 56 , 5 options
        //equals 1 if sumOfRandoms = 29 or 36 or 43 or 50, 4 options
        //equals 2 if sumOfRandoms = 30 or 37 or 44 or 51, 4 options
        //equals 3 if sumOfRandoms = 31 or 38 or 45 or 52, 4 options
        //equals 4 if sumOfRandoms = 32 or 39 or 46 or 53, 4 options
        //equals 5 if sumOfRandoms = 33 or 40 or 47 or 54, 4 options
        //equals 6 if sumOfRandoms = 34 or 41 or 48 or 55, 4 options
        //It means that the probabilities of getting numbers between 0 and 6 are almost equal.
        int result = sumOfRandoms % 7;
        //we should add 1 to move the interval [0,6] to the interval [1,7]
        return 1 + result;
    }


    如果有人能给我关于这个的反馈,那就太酷了,我使用了没有断言模式的JUnit,因为在Eclipse中运行它既简单又快速,我还可以定义一个主要方法。顺便说一句,我假设rand5给出的值是0-4,加上1会得到1-5,和rand7一样…所以讨论的应该是解决方案,它的分布,而不是从0-4或1-5…

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    package random;

    import java.util.Random;

    import org.junit.Test;

    public class RandomTest {


        @Test
        public void testName() throws Exception {
            long times = 100000000;
            int indexes[] = new int[7];
            for(int i = 0; i < times; i++) {
                int rand7 = rand7();
                indexes[rand7]++;
            }

            for(int i = 0; i < 7; i++)
                System.out.println("Value" + i +":" + indexes[i]);
        }


        public int rand7() {
            return (rand5() + rand5() + rand5() + rand5() + rand5() + rand5() + rand5()) % 7;
        }


        public int rand5() {
            return new Random().nextInt(5);
        }


    }

    当我运行它时,会得到以下结果:

    1
    2
    3
    4
    5
    6
    7
    Value 0: 14308087
    Value 1: 14298303
    Value 2: 14279731
    Value 3: 14262533
    Value 4: 14269749
    Value 5: 14277560
    Value 6: 14304037

    这似乎是一个非常公平的分配,不是吗?

    如果我添加rand5()的次数少于或多于7次(其中,次数不能被7整除),那么分布将清楚地显示偏移量。如增加rand5()3次:

    1
    2
    3
    4
    5
    6
    7
    Value 0: 15199685
    Value 1: 14402429
    Value 2: 12795649
    Value 3: 12796957
    Value 4: 14402252
    Value 5: 15202778
    Value 6: 15200250

    因此,这将导致以下结果:

    1
    2
    3
    4
    5
    6
    7
    8
    public int rand(int range) {
        int randomValue = 0;
        for(int i = 0; i < range; i++) {
            randomValue += rand5();
        }
        return randomValue % range;

    }

    然后,我可以更进一步:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    public static final int ORIGN_RANGE = 5;
    public static final int DEST_RANGE  = 7;

    @Test
    public void testName() throws Exception {
        long times = 100000000;
        int indexes[] = new int[DEST_RANGE];
        for(int i = 0; i < times; i++) {
            int rand7 = convertRand(DEST_RANGE, ORIGN_RANGE);
            indexes[rand7]++;
        }

        for(int i = 0; i < DEST_RANGE; i++)
            System.out.println("Value" + i +":" + indexes[i]);
    }


    public int convertRand(int destRange, int originRange) {
        int randomValue = 0;
        for(int i = 0; i < destRange; i++) {
            randomValue += rand(originRange);
        }
        return randomValue % destRange;

    }


    public int rand(int range) {
        return new Random().nextInt(range);
    }

    我尝试将destrange和originrange替换为不同的值(即使7代表原始值,13代表目标值),我得到以下分布:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    Value 0: 7713763
    Value 1: 7706552
    Value 2: 7694697
    Value 3: 7695319
    Value 4: 7688617
    Value 5: 7681691
    Value 6: 7674798
    Value 7: 7680348
    Value 8: 7685286
    Value 9: 7683943
    Value 10: 7690283
    Value 11: 7699142
    Value 12: 7705561

    我从这里得出的结论是,你可以通过求出原点随机的"目的地"时间,将任意一个随机变量变为任意其他变量。这将得到一种高斯分布(更可能是中间值,边缘值更不常见)。然而,目的地模量似乎在高斯分布上均匀分布…如果能得到数学家的反馈…

    很酷的是,成本是100%可预测和不变的,而其他解决方案导致无限循环的概率很小…


    你为什么不先除以5再乘以7,然后取整呢?(当然,您必须使用浮点数s)

    它更容易,更可靠(真的吗?)比其他解决方案。例如在Python中:

    1
    2
    3
    4
    5
    def ranndomNo7():
        import random
        rand5 = random.randint(4)    # Produces range: [0, 4]
        rand7 = int(rand5 / 5 * 7)   # /5, *7, +0.5 and floor()
        return rand7

    这不容易吗?


    这个表达式足以得到1-7之间的随机整数。

    1
    int j = ( rand5()*2 + 4 ) % 7 + 1;


    这个问题的主要概念是正态分布,这里提供了一个简单而递归的解决方案。

    假设我们的范围内已经有rand5()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    def rand7():
        # twoway = 0 or 1 in the same probability
        twoway = None
        while not twoway in (1, 2):
            twoway = rand5()
        twoway -= 1

        ans = rand5() + twoway * 5

        return ans if ans in range(1,8) else rand7()

    解释

    我们可以将这个程序分为两部分:

  • 循环rand5(),直到我们找到1或2,这意味着我们有1/2的概率在变量twoway中有1或2。
  • rand5() + twoway * 5合成的ans,这正是rand10()的结果,如果这不符合我们的需要(1~7),那么我们再次运行rand7。
  • 另外,我们不能在第二部分直接运行while循环,因为twoway的每个概率都需要是独立的。

    但是有一个权衡,因为第一节中的while循环和返回语句中的递归,这个函数不能保证执行时间,实际上是无效的。

    结果

    我做了一个简单的测试来观察我答案的分布情况。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    result = [ rand7() for x in xrange(777777) ]

    ans = {
        1: 0,
        2: 0,
        3: 0,
        4: 0,
        5: 0,
        6: 0,
        7: 0,
    }

    for i in result:
        ans[i] += 1

    print ans

    它给予

    1
    {1: 111170, 2: 110693, 3: 110651, 4: 111260, 5: 111197, 6: 111502, 7: 111304}

    所以我们可以知道这个答案是正态分布。

    简化答案

    如果您不关心此函数的执行时间,下面是基于我给出的上述答案的简化答案:

    1
    2
    3
    def rand7():
        ans = rand5() + (rand5()-1) * 5
        return ans if ans < 8 else rand7()

    这增加了值大于8的概率,但可能是这个问题的最短答案。


    1
    2
    3
    4
    5
    6
    7
    8
    def rand5():
        return random.randint(1,5)    #return random integers from 1 to 5

    def rand7():
        rand = rand5()+rand5()-1
        if rand > 7:                  #if numbers > 7, call rand7() again
            return rand7()
        print rand%7 + 1

    我想这将是最简单的解决方案,但无论在哪里,人们都建议使用5*rand5() + rand5() - 5,比如http://www.geeksforgeks.org/generate-integer-from-1-to-7-with-equal-probability/。有人能解释一下rand5()+rand5()-1出了什么问题吗?


    面对这些复杂的回答,我觉得自己很傻。

    为什么不能:

    1
    2
    3
    4
    int random1_to_7()
    {
      return (random1_to_5() * 7) / 5;  
    }


    这是我想到的答案,但这些复杂的答案让我觉得这完全是错误的/:)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    import random

    def rand5():
        return float(random.randint(0,5))

    def rand7():
        random_val = rand5()
        return float(random.randint((random_val-random_val),7))

    print rand7()


    一种产生近似均匀分布的常数时间解。诀窍是625恰好可以被7完全除尽,当您建立到该范围时,您可以获得均匀分布。

    编辑:我的错,我算错了,但我不会拉它,我会留下它,以防有人发现它有用/有趣。毕竟它确实有效…:)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    int rand5()
    {
        return (rand() % 5) + 1;
    }

    int rand25()
    {
        return (5 * (rand5() - 1) + rand5());
    }

    int rand625()
    {
        return (25 * (rand25() - 1) + rand25());
    }

    int rand7()
    {
        return ((625 * (rand625() - 1) + rand625()) - 1) % 7 + 1;
    }


    标准方法如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    /**
     * Returns a pseudo-random number between min and max, inclusive.
     * The difference between min and max can be at most
     * <wyn>Integer.MAX_VALUE - 1</wyn>.
     *
     * @param min Minimum value
     * @param max Maximum value.  Must be greater than min.
     * @return Integer between min and max, inclusive.
     * @see java.util.Random#nextInt(int)
     */
    public static int randInt(int min, int max) {

        // NOTE: Usually this should be a field rather than a method
        // variable so that it is not re-seeded every call.
        Random rand = new Random();

        // nextInt is normally exclusive of the top value,
        // so add 1 to make it inclusive
        int randomNum = rand.nextInt((max - min) + 1) + min;

        return randomNum;
    }

    传递最小-最大值此函数将返回介于最小-最大值之间的值


    1
    2
    3
    4
    5
    int rand7()
    {
        int zero_one_or_two = ( rand5() + rand5() - 1 ) % 3 ;
        return rand5() + zero_one_or_two ;
    }


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    #!/usr/bin/env ruby
    class Integer
      def rand7
        rand(6)+1
      end
    end

    def rand5
      rand(4)+1
    end

    x = rand5() # x => int between 1 and 5

    y = x.rand7() # y => int between 1 and 7

    …尽管这可能被认为是作弊。


    PHP解决方案

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    <?php
    function random_5(){
        return rand(1,5);
    }


    function random_7(){
     $total = 0;

        for($i=0;$i<7;$i++){
            $total += random_5();
        }

        return ($total%7)+1;
    }

    echo random_7();
    ?>


    我已经到处玩过了,我为这个rand(7)算法编写了"测试环境"。例如,如果您想尝试什么分布给您的算法,或者需要多少迭代才能生成所有不同的随机值(对于rand(7)1-7),您可以使用它。

    我的核心算法是:

    1
    return (Rand5() + Rand5()) % 7 + 1;

    井的分布也不比亚当·罗森菲尔德的均匀。(我把它包括在我的代码片段中)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    private static int Rand7WithRand5()
    {
        //PUT YOU FAVOURITE ALGORITHM HERE//

        //1. Stackoverflow winner
        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;

        //My 2 cents
        //return (Rand5() + Rand5()) % 7 + 1;
    }

    这个"测试环境"可以采用任何rand(n)算法,并对其进行测试和评估(分布和速度)。只需将代码放入"rand7withrand5"方法并运行代码片段。

    很少观察到:

    • 例如,亚当·罗森菲尔德(AdamRosenfield)的算法并没有比我的算法更好地分布。总之,这两种算法的分布都很糟糕。
    • 本地rand7(random.Next(1, 8)完成,因为它在200+次迭代中生成给定间隔内的所有成员,rand7与rand5算法的顺序为10 K(约30-70 K)
    • 真正的挑战不是写一个方法来从rand(5)生成rand(7),而是生成或多或少均匀分布的值。