【问题标题】:Expand a random range from 1–5 to 1–7将随机范围从 1-5 扩展到 1-7
【发布时间】:2010-09-13 08:38:21
【问题描述】:

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

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

【问题讨论】:

  • 事实证明这是一个意想不到的有趣问题,我仍然在想如何1)在固定的时间内完成2)不破坏均匀分布(如果有的话)
  • 我们在用骰子从 5 名玩家中选择一名玩家时遇到了类似的问题。我们轮流掷骰子,选择得分最高的人。达到了均匀性,但没有达到时间常数:)
  • 如果我发布了一个回答说问题并不要求你必须使用给定的函数并且只写一个随机返回 1-7 的答案,我会否被否决?
  • 7 * rand5() / 5 呢?
  • @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。

标签: algorithm random puzzle


【解决方案1】:

这里允许作业问题吗?

此函数执行粗略的“以 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;
}

【讨论】:

  • 一个正确的解决方案(它让你走在曲线的前面),虽然不是很有效。这使得每次调用 rnd7() 平均调用 5 次 rnd5()。
  • 需要更多解释
  • @Barry - 首先,你不能只是将两个随机数相加,你不会得到线性解决方案(考虑一对骰子)。现在考虑“Base 5”:00, 01, 02, 03, 04, 10, 11。base 5 中的 0-6。所以,我们只需要生成 base 5 的 2 位数字,并将它们相加直到我们得到一个在范围内的。这就是 r2*5+r1 的作用。存在 r2 > 1 循环是因为我们永远不会想要 > 1 的高位。
  • 此解决方案不会生成均匀分布。数字 1 和 7 只能以一种方式生成,但 2 到 6 可以分别以两种方式生成:r1 等于数字负 1 且 r2 等于 0 或 r1 等于数字负 2 且 r2 等于1. 因此 2 到 6 的平均返回频率是 1 或 7 的两倍。
【解决方案2】:

没有(完全正确的)解决方案可以在恒定时间内运行,因为 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 次循环迭代的预期运行时间,但永远循环的可能性非常小。

【讨论】:

  • 如果 >21 翻转到 >26 b/c,则不需要 -1,无论 i 的下限映射到哪里,
  • 我解释为什么这是正确的:假设我想编写一个程序,输出从 1 到 25 的统一随机数流;为此,我只返回 5 * (rand5() - 1) + rand5() ,如答案中的代码所示。现在,如果我想构建一个介于 1 和 21 之间的统一随机数流,如果我只使用第一个流但过滤它以拒绝 [22, 25] 中的数字,我也可以构建该流。接下来,如果我获取这个流并对其进行过滤,以便对于每个元素 x 我输出 x % 7 + 1,我就有一个从 1 到 7 的统一随机数流!很简单,不是吗? :D
  • 你是对的,归结为你想要一个具有无限最坏情况运行时间的完美分布,还是一个具有有限运行时间的不完美分布。这是因为所有 5 的幂都不能被 7 整除,或者等效地,如果您有 5^n 个长度为 n 的序列,则无法为每个序列分配一个从 1 到 7 的数字,这样每个序列1..7 同样可能。
  • @Jules Olléon:假设有一个在恒定时间内运行的解决方案,在最坏的情况下保证不会超过Nrand5() 的调用。然后,对rand5 的调用序列有 5^N 个可能的结果,每个结果的输出为 1-7。因此,如果将所有可能的调用序列相加,其输出为k,每个 1≤k≤7,则输出为k 的概率为 m/5^N,其中 m 是这样的序列。所以,m/5^N = 1/7,但是对于这个 ==> 矛盾没有可能的整数解 (N,m)。
  • @paxdiablo:你错了。使用与flipping a coin an infinite number of times is guaranteed not to generate an infinite number of consecutive heads 类似的推理,真正的 RNG 生成无限 5 序列的机会正好是 0。这也意味着这段代码永远循环的机会正好是 0(尽管它很有可能会循环任意次数的迭代)。
【解决方案3】:
int ans = 0;
while (ans == 0) 
{
     for (int i=0; i<3; i++) 
     {
          while ((r = rand5()) == 3){};
          ans += (r < 3) >> i
     }
}

【讨论】:

  • 一个正确的解决方案,平均每次调用 rand7() 调用 30/7 = 4.29 次 rand5()。
  • 需要 left shift 算法才能工作:ans += (r &lt; 3) &lt;&lt; i
【解决方案4】:
rand7() = (rand5()+rand5()+rand5()+rand5()+rand5()+rand5()+rand5())%7+1

编辑:那不太行。它大约相差 1000 分之二(假设一个完美的 rand5)。桶得到:

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] 是在n 调用rand5output=x 可能发生的方式。

  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]

【讨论】:

  • 这不是均匀分布。它非常接近均匀,但不是完全均匀。
  • 啊!骰子和 7。如果你要说我错了,你不应该把证明留给读者练习。
  • 它不均匀的证明很简单:随机性有 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 那么,如果我们不将 rand5() 的总和七次,而是 5*7 次,那会不会奏效? 35^7 % 7 = 35^5 % 7 = 0。
  • @KristianAntonsen:你做了多少次 rand5(),你不会得到均匀分布。如果你做 N 次,有 5^N 可能的输出,不能被 7 整除。(如果你做 35 次,有 5^35,而不是 35^7。)你会越来越接近统一您使用的大量调用(它可以是任何数字,不必被 7 整除),但恕我直言,而不是使用大量调用 rand(),您也可以使用概率最佳答案中的算法,它给出了精确的均匀分布,并且对 rand() 的预期调用次数很小。
【解决方案5】:
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 );
    }
}

【讨论】:

  • 一个正确的解决方案,平均每次调用 rand7() 调用 30/7 = 4.29 次 rand5()。
【解决方案6】:

php 中的解决方案

<?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();
?>

【讨论】:

    【解决方案7】:

    以下使用随机数生成器在 {1,2,3,4,5,6,7} 上生成均匀分布,在 {1,2,3,4,5} 上生成均匀分布。代码乱七八糟,但逻辑清晰。

    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;
    }    
    

    【讨论】:

    • 一个正确的解决方案(它让你走在曲线的前面),虽然不是很有效。这使得每次公平掷硬币平均调用 random_5_mod_2 25/6 = 4.17 次,每次调用 random_7() 时平均调用 random_5() 100/7 = 14.3 次。
    • 此解决方案优于其他解决方案的优势在于它可以轻松扩展以生成任何其他均匀分布的范围。只需随机选择每一位,重新滚动无效值(如我们当前解决方案中的 0 值,产生 8 个数字)。
    • 可能的无限循环等
    • @robermorales:极不可能。
    【解决方案8】:

    假设这里的rand(n)意思是“从0n-1均匀分布的随机整数”,下面是代码使用 Python 的 randint 的示例,它具有这种效果。它仅使用 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
    

    【讨论】:

    • @robermorales 因为 Python 没有 do ... while。可能是1337,或12345,或任何大于1的数字。
    【解决方案9】:

    (我偷了Adam Rosenfeld's answer,让它运行速度提高了大约 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()
    

    【讨论】:

    • 在 1,000,000 次尝试中编目的结果:1=47216; 2=127444; 3=141407; 4=221453; 5=127479; 6=167536; 7=167465。如您所见,在获得 1 的几率方面缺乏分布。
    • @The Wicked Flea:我认为你错了。您确定您用于测试的输入 rand5() 产生了 0-4 而不是 1-5,正如本解决方案中指定的那样?
    • 添加均匀分布的数字不会导致均匀分布的数字。实际上,您只需要对 6 个这样的均匀分布变量求和即可得到一个合理的正态分布近似值。
    • @MitchWheat - 实际上,添加两个均匀分布的整数确实会产生一个均匀分布的随机整数,前提是每个可能的总和都可以以一种方式生成。这恰好是表达式5 * rand5() + rand5() 中的情况。
    【解决方案10】:

    产生近似均匀分布的恒定时间解。 诀窍是 625 恰好可以被 7 整除,当你建立到这个范围时,你可以获得均匀的分布。

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

    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;
    }
    

    【讨论】:

    • "625 恰好可以被 7 整除" - 再猜一次。 625 = 5^4 不能被 7 整除。
    • 谢谢,你说的很对。 Apple 的计算器对我撒谎(或者我忘记了它在“程序员”模式下没有小数)。
    【解决方案11】:
    int rand7()
    {
        int zero_one_or_two = ( rand5() + rand5() - 1 ) % 3 ;
        return rand5() + zero_one_or_two ;
    }
    

    【讨论】:

      【解决方案12】:

      在所有这些复杂的答案面前我感到很愚蠢。

      为什么不能:

      int random1_to_7()
      {
        return (random1_to_5() * 7) / 5;  
      }
      

      ?

      【讨论】:

      • 测试一下 - 它不起作用。它不会提供所有 7 个数字的均匀分布。
      • 如果我们对实数感兴趣,这将起作用,但由于我们处理的是整数,因此该代码只会产生 1、2、4、5 或 7,而不会产生 3 或 6。
      【解决方案13】:
      #!/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
      

      ..虽然这可能被认为是作弊..

      【讨论】:

        【解决方案14】:

        我玩过,我为这个 Rand(7) 算法写了"testing environment"。例如,如果您想尝试为您的算法提供什么分布,或者需要多少次迭代才能生成所有不同的随机值(对于 Rand(7) 1-7),请can use it

        我的核心算法是这样的:

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

        Well 的分布不亚于 Adam Rosenfield 的分布。 (which I included in my snippet code)

        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”方法并运行 sn-p。

        几个观察:

        • Adam Rosenfield 的算法没有比我的算法更好的分布,例如我的算法。无论如何,这两种算法的分布都很糟糕。
        • Native Rand7 (random.Next(1, 8)) 已完成,因为它在大约 200 多次迭代中以给定的间隔生成了所有成员,Rand7WithRand5 算法的数量级为 10k(大约 30-70k)
        • 真正的挑战不是编写从 Rand(5) 生成 Rand(7) 的方法,而是生成或多或少均匀分布的值。

        【讨论】:

        • 不,您的算法不会产生均匀分布。它产生 1..7,概率为 4/25、3/25、3/25、3/25、3/25、4/25、5/25,可以通过计算所有 25 个可能的结果来轻松验证。 25 不能被 7 整除。您的一致性测试也存在缺陷——获得每个数字所需的试验次数具有复杂的分布,请参阅is.gd/wntB。您需要执行数千次测试,而不是一次。更好的测试是调用 RNG 数千次并比较每个结果的出现次数。
        【解决方案15】:

        这是Adam's 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() 的规则。最后,您会产生非均匀分布。
        • 很抱歉。当我看这个问题时,我很累,累到我完全误读了你的算法。我实际上把它扔进了 Python,因为我不明白你为什么要循环 21 次。现在更有意义了。我做了 random.randint(1, 4) 事情作为速记,但我想你是对的,这违背了问题的精神。我已经更正了代码。
        • @robermorales - 正如 Adam Rosenfeld 在his answer 中解释的那样,在 [1, 7] 上给出真正均匀分布的每个解决方案都将涉及某种可能无限的接受-拒绝循环。 (但是,如果 rand5() 是一个不错的 PRNG,那么循环不会是无限的,因为最终 5*(rand5() - 1) + rand5() 肯定会
        【解决方案16】:

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

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

        这两个问题都是简单的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)
        

        【讨论】:

        • 错了,让我换个说法。假设在序列中的某个点产生了特定的 x,则只能为序列中的下一个数字产生 7 个数字中的 5 个。真正的 RNG 会让所有样本相互独立,但在这种情况下,它们显然不是。
        • 确实原始问题没有指定输入和输出函数是否产生独立且同分布(iid)的样本,但我认为如果输入 rand5() 是iid,那么输出 rand7() 也应该是 iid。如果您认为这不合理,请享受使用非 iid RNG 的乐趣。
        • 那么,大学里的数学家怎么说?
        • 这个解决方案显然被打破了。很明显,每次调用 rand7 时,您需要多次调用 rand5(平均),而这个解决方案不需要。因此,根据任何合理的随机定义,结果都不能是随机的。
        • @Pax 在函数的每次迭代中,它只能返回五个不同值之一(尽管在 0-6 范围内)。第一次迭代只能返回 0-4 范围内的数字。因此,应该清楚的是,虽然您的函数可能具有均匀分布,但样本不是独立的,即它们是相关的,这在随机数生成器中不是您想要的。
        【解决方案17】:

        这相当于 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 索引随机选择飞镖板上的一个位置,如果我们没有得到好的结果,我们会继续投掷飞镖。

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

        【讨论】:

        • 我理解了这个解决方案背后的逻辑,但无法理解它是如何产生统一概率的?有人能解释一下数学吗?
        • @user1071840 - 如果rand5 是统一的,则vals 网格中的每个单元格被选中的概率相同。网格恰好包含区间 [1, 7] 中每个整数的三个副本,外加四个零。因此,“原始”结果流趋向于 [1, 7] 值的均匀混合,加上一些比任何单个允许值更频繁出现的零。但这并不重要,因为零被去除了,只剩下 [1, 7] 值的均匀混合。
        • 解决问题的捷径:如果你只调用 rand5() 一次,那么你只有 5 种可能的结果。如果不增加更多随机性,显然没有办法将其转化为超过 5 种可能的结果。
        • 更长的版本:rand5() 只能有值 (1, 2, 3, 4, 5)。因此 rand5() * 5 只能有值 (5, 10, 15, 20, 25),这与完整范围 (1...25) 不同。如果是这样,减去 4 会得到 (-3...21),但在这种情况下,它会变成 (1, 6, 11, 16, 21),所以端点是正确的,但是有四个大洞:( 2..5)、(7..10)、(12..15)、(17..21)。最后你做 mod 7 并加 1,得到 (2, 7, 5, 3, 1)。所以 4 和 6 都不会出现。但是(见上面的快捷方式)我们知道结果范围内只能有 5 个数字,所以必须有两个差距。
        • 你把零放在最后有什么原因吗?
        【解决方案18】:

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

        分析这一点的最简单方法是将流 I 和 O 分别视为 5 进制数和 7 进制数。这是通过主要答案采用流a1, a2, a3,... -&gt; a1+5*a2+5^2*a3+.. 的想法实现的,对于流O 也是如此。

        如果我们取一段长度为m choose n s.t. 5^m-7^n=c 的输入流,其中c&gt;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&lt;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)

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

        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+ss&lt;7

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

        最坏的情况是我们只能找到 k 和 s.t 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 位整数的第二好的答案。
        【解决方案19】:

        这个答案更像是一个从 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 的比率是 2.125 次调用 Rand5。最佳值应该是 2.1。

        我想这种方法比这里的许多其他方法明显慢,除非调用 Rand5 的成本非常昂贵(比如调用一些外部熵源)。 p>

        【讨论】:

        • 您的解决方案似乎是正确的,除了一些简单的错误:“if(count > 1)”应该是“if(count
        • 不过,总的来说,你的函数很难理解。它确实比其他方法更好地利用了熵,但代价是更复杂。也没有理由在第一次调用时用 35 个随机位填充缓冲区,而 3 就足够了。
        • 我更正了
        • 我对其进行了重新设计,使其更易于理解(以速度为代价),但也使其正确。它还不是最优的,由于某种原因,即使它们的数量相同,1/5 位也会导致问题。
        【解决方案20】:

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

        entropy of a random variable 是一个定义明确的数量。对于具有相等概率(均匀分布)的 N 个状态的随机变量,熵为 log2 N。因此,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 为所有 i 选择了一个i = 1,那么忽略这不是非常随机的事实,这将对应于实数 1/5 + 1 /52 + 1/53 + ... = 1/4(几何级数之和)。

        好的,所以我们选择了一个介于 0 和 1 之间的随机实数。我现在声称这样的随机数是均匀分布的。直观地说,这很容易理解,因为每个数字都是统一挑选的,而且数字是无限精确的。然而,对此的正式证明有点复杂,因为现在我们处理的是连续分布而不是离散分布,所以我们需要证明我们的数字位于区间内的概率 [a, @987654342 @] 等于该间隔的长度,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 等。

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

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

        1. 乘以 7
        2. 结果的整数部分是下一个以 7 为基数的数字
        3. 减去整数部分,只留下小数部分
        4. 转到第 1 步

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

        因此,为了跟踪目前的数字,以及它可以取的最大值,我们将 两个 数字都转换为以 7 为底。如果它们在第一个 k 数字上一致,那么我们可以安全地输出下一个 k 数字——无论基数为 5 的无限流是什么,它们都不会影响基数为 7 表示的下一个 k 数字!

        这就是算法——为了生成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() 进行了 12091 次调用,对rand7() 进行了 10000 次调用,实现了 log(7)/log(5) 调用的最小值,平均为 4 个有效数字,并且得到的输出是统一的。

        为了将此代码移植到没有内置任意大整数的语言,您必须将 pow5pow7 的值限制为您的本机整数类型的最大值 - - 如果它们变得太大,则重置所有内容并重新开始。这将非常轻微地增加每次调用rand7() 的平均调用次数rand5(),但希望即使对于32 位或64 位整数也不会增加太多。

        【讨论】:

        • +1 以获得非常有趣的答案。是否有可能,而不是重置为某个值,而是简单地移出已使用的位,并将其他位向上移动,并且基本上只保留将要使用的位?还是我错过了什么?
        • 我不是 100% 确定,但我相信如果你这样做,你会稍微偏斜分布(尽管我怀疑这种偏斜在没有数万亿次试验的情况下是可以测量的)。跨度>
        • FTW!我试图让 bignums 更小,但它无法做到,因为没有 5 的幂与 7 的幂有共同的因素!此外,善用 yield 关键字。做得很好。
        • 非常好!我们可以在不增长状态的情况下保留额外的熵吗?诀窍是要注意上限和下限始终都是有理数。我们可以在不损失精度的情况下对它们进行加减乘乘。如果我们在 base-35 中完成这一切,我们就快到了。余数(乘以 7 并保留小数部分)留作练习。
        • @Isaac:不,这没有有限的运行时间。没有完全正确的答案可以有有限的运行时间。
        【解决方案21】:

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

        • 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 没有可能的丢弃值,我还没有找到。

        【讨论】:

        • 可以证明没有一次性值的情况——如果没有一次性值,5^n 和 7^m 将有一个共同因素。但它们是素数的(幂),所以它们不是。
        【解决方案22】:

        这是我的答案:

        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 的调用。与其他解决方案一样,它可能会循环很长时间。

        【讨论】:

        • 这产生了一个与其他解决方案没有太大区别的分布,但有一个额外的缺点是不必要的复杂。如果数字真的是随机的,它还会遭受可证明不正确的非确定性循环永远的可能性。我仍然认为那些产生稍微不那么均匀的分布(尽管仍然远远超过足够)但保证确定性行为的效果更好。
        • @Pax:请告诉我这是如何产生非均匀分布的。我对代码的分析以及我自己的测试表明,这会产生均匀分布。正如我们之前所讨论的,不可能既产生完全均匀的分布,又保证运行时间的恒定时间上限。
        【解决方案23】:

        上面引用了一些优雅的算法,但这里有一种方法可以接近它,尽管它可能是迂回的。我假设从 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
        .
        .
        1 1 1 --> 7

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

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

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

        【讨论】:

        • 与其他许多方法一样,这种方法每次 R7 调用可能会花费任意长的时间,因为您可能会从 R8 得到一长串的 7。
        【解决方案24】:

        为什么不简单呢?

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

        由于模数,在此解决方案中获得 1 和 7 的机会较低,但是,如果您只是想要一个快速且易读的解决方案,那么这是要走的路。

        【讨论】:

        • 这不会产生均匀分布。这会产生概率为 2/25、4/25、5/25、5/25、5/25、3/25、1/25 的数字 0-6,这可以通过计算所有 25 个可能的结果来验证。跨度>
        【解决方案25】:

        只要没有七种可能性可供选择,就再画一个随机数,将可能性的数量乘以五。在 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)。
        【解决方案26】:

        你需要的函数是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
        

        【讨论】:

          【解决方案27】:

          你去吧,均匀分布和零 rand5 调用。

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

          需要提前播种。

          【讨论】:

            【解决方案28】:

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

            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
            

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

            【讨论】:

              【解决方案29】:

              我知道它已被回答,但这似乎可以正常工作,但我不能告诉你它是否有偏见。我的“测试”表明它至少是合理的。

              也许 Adam Rosenfield 愿意发表评论?

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

              累加 rand5 直到有足够的随机位组成 rand7。这最多需要 2 个 rand5。要获得 rand7 数,我使用累积值 mod 7。

              为了避免累加器溢出,并且由于累加器是模7,所以我取累加器的模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

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

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

              【讨论】:

              • 你应该看看顺序相关。我认为,如果您采用连续的配对(每个“随机”数字与其前任配对),那么您可能会发现令人惊讶的事情。无论如何,您还没有解释为什么它应该保持分布均匀。一个工作程序通常应该以解释它为什么工作的原因开始。
              • 顺序相关是否适用于这些解决方案中的许多?
              • 顺序相关是否适用于这些解决方案中的许多?自从我尝试这个已经有一段时间了,我想我已经解释过了。现在看,看起来我正在从 rand5 的池中累积随机位,确保在提取足够多的 rand7 数字之前已经累积了足够多的位,并确保我的累加器不会溢出。
              【解决方案30】:

              只需从您的第一个函数扩展您的输出

              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) 中,您有 5 个不同的值。步骤 (2) 扩大了范围,但不增加不同值的数量,因此最后仍然只有 5 个值。
              猜你喜欢
              • 2017-08-20
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2016-02-09
              • 1970-01-01
              相关资源
              最近更新 更多