Expand a random range from 1–5 to 1–7
给定一个在1到5范围内产生随机整数的函数,编写一个在1到7范围内产生随机整数的函数。
这相当于亚当·罗森菲尔德的解决方案,但对一些读者来说可能更清楚一些。它假设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次循环,但永远循环的概率是无穷小的。
除了我的第一个答案,我想再加一个答案。此答案试图最小化每次调用
随机变量的熵是一个定义明确的量。对于具有相同概率(均匀分布)的n个状态的随机变量,熵为log2n。因此, 旁注:除非另有规定,否则本答案中的所有对数均以2为底。假设 那我们该怎么做呢?我们生成了一个介于0和1之间的无限精确随机实数(暂时假设我们可以计算并存储这样一个无限精确的数字——稍后我们会解决这个问题)。我们可以通过在基数5中生成数字来生成这样一个数字:我们选择随机数0。 好的,我们选择了一个介于0和1之间的随机实数。我现在声称这样一个随机数是均匀分布的。直观地说,这很容易理解,因为每个数字都是统一选取的,而且数字的精确性是无限的。然而,关于这一点的正式证明更为复杂,因为我们现在处理的是连续分布而不是离散分布,所以我们需要证明,我们的数字存在于区间的概率[ 既然我们有一个从范围[0,1]中均匀选择的随机实数,我们需要将它转换为范围[0,6]中的一系列均匀随机数,以生成 以前面的例子为例,如果我们的 好的,我们有一个主要的想法,但是我们还有两个问题:我们不能计算或存储一个无限精确的实数,那么我们如何处理它的有限部分呢?其次,我们如何将它转换为7进制呢?好的。 我们可以将0到1之间的数字转换为以7为基数的方法如下:好的。 为了解决无限精度的问题,我们计算了一个局部结果,并且我们还存储了一个关于结果可能是什么的上界。也就是说,假设我们给 所以,保持对当前数字的跟踪,以及它可能需要的最大值,我们将这两个数字都转换为7。如果他们同意第一个 这就是算法——为了生成 注意, 还要注意,这里的数字非常大,非常快。5和7的力量增长很快。因此,由于bignum算法的存在,在生成大量随机数之后,性能将开始显著下降。但请记住,我的目标是最大化随机位的使用,而不是最大化性能(尽管这是第二个目标)。好的。 在一次运行中,我给 为了将此代码移植到一种没有内置任意大整数的语言中,您必须将 (我偷了亚当·罗森菲尔德的答案,让它跑得快7%左右。) 假设rand5()返回0,1,2,3,4中的一个,分布相等,目标是返回0,1,2,3,4,5,6,分布相等。 我们跟踪循环在变量 编辑:期望调用rand5()的次数在此公式中为x:
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
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)
2
3
4
5
6
7
8
9
10
11
12
13
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);
}
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 waysoutput=x can happen givenn calls torand5 .
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和
然后,如果我们取一段长度为
因此,这给了
上述分析的难点是方程
问题是M(log5/log7)的最佳可能值接近多少。例如,当这个数字接近整数时,我们能找到一种方法来实现输出值的这个精确整数吗?
如果
如果我们让
然后是
如果我们继续替换,我们得到:
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) |
最好的情况是我在上面的原始案例,其中
然后像以前一样,以东十一〔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) |
一般来说,实现
然后,整个问题取决于
我相信有很多理论可以涵盖这一点,我可能会在某个时候回顾和报告。
这里允许作业问题吗?
这个函数执行粗略的"基数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的成本非常昂贵(比如调用外部熵源)。
通过使用滚动总数,可以同时使用
- 保持平等分配;以及
- 不必牺牲随机序列中的任何元素。
这两个问题都与简化的
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%) |
一个简单的
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%) |
而且,根据尼克苏兹的建议,我已经清理了脚本,这样您就可以提取和使用
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 |
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 |
只需从第一个函数缩放输出
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()); } } |
假设
1 | int i = rand(5) ^ (rand(5) & 2); |
以下是我发现的:
然后我们得到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
我们这里使用的是惯例
从我读到的许多答案来看,它们要么提供一致性保证,要么提供暂停保证,但不能同时提供两者(亚当·罗森菲尔德的第二个答案可能)。
然而,这样做是可能的。我们基本上有这样的分布:
这给我们在
实际上,我们可以将初始分布本身移动一个,并且通过将获得的分布与初始分布相加重复2,然后3等等,直到7,不包括在内(我们覆盖了整个范围)。如下图所示。颜色的顺序,对应于这些步骤是蓝色、绿色、青色、白色、洋红、黄色、红色。
因为每个槽被7个移位分布中的5个覆盖(移位随0到6),因为我们假设随机数与1无关
1 | p(x) = 5 / 35 = 1 / 7 for all x in [0, 6] |
这意味着,给定来自
这为我们提供了以下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)]) |
如果我们给
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个电话给
这个解决方案不浪费任何熵,并给出了范围内第一个真正可用的随机数。每一次迭代都会减少得不到答案的概率。在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个
在我提出的解决方案中,我只打一次电话给
实解
1 2 3 4 | float rand7() { return (rand5() * 7.0) / 5.0 ; } |
这里的分布是按比例分布的,所以它直接取决于
整数解
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; } |
这里的分布取决于系列
与
我想我有四个答案,两个给出了像@adam rosenfield那样的精确解,但没有无限循环问题,另外两个给出了几乎完美的解,但比第一个更快的实现。
最好的精确解决方案需要7次调用
Adam的答案的优势在于它提供了一个完美的统一分布,并且非常有可能(21/25)只需要两个对rand5()的调用。然而,最坏的情况是无限循环。
下面的第一个解决方案也提供了一个完美的统一分布,但总共需要42个调用
下面是一个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 } |
保留
以下是所有可能的组合:
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个或更多的调用
现在,第二个方法几乎是统一的,但需要6个对
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 |
一个数字将再次出现在
现在,在方法1中,通过添加1到6,我们将数字2233移动到每个连续的点上。因此,组合的总数将匹配起来。这是因为5^6%%7=1,然后我们做7个适当的变化,所以(7*5^6%%7=0)。
方法3-精确如果方法1和2的参数被理解,那么方法3将遵循,并且只需要7个对
下面是一个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 } |
保留
以下是所有可能的组合:
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个或更多调用
这是第二种方法的微小变化。它几乎是统一的,但需要7个对
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 |
两个数字将在
另一个似乎未涵盖的答案是:
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; } |
在每次迭代中,
这种方法并不完全一致;但是,偏差非常小。按1/(2**64)的顺序排列的东西。这种方法的重要之处在于它有恒定的执行时间(假设
另外,一个对良好措施的讽刺性回答(不管是有意还是无意,它已经被涵盖):
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) |
结果:
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) ); } |
我想到的第一件事就是这个。但我不知道它是否均匀分布。在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)这将把随机生成器转换为浮点生成器。我将在这里定义两个参数:
b 和p 。虽然这里的"基数"是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;
}
}在这里,我们在
0 和m-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),而是生成或多或少均匀分布的值。