给定一个产生1到5范围内的随机整数的函数,编写一个产生1到7范围内的随机整数的函数。
- 什么是简单的解决方案?
- 什么是减少内存使用或在较慢的CPU上运行的有效解决方案?
7 * rand5() / 5
呢
给定一个产生1到5范围内的随机整数的函数,编写一个产生1到7范围内的随机整数的函数。
7 * rand5() / 5
呢
Answers:
这等效于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索引在飞镖板上随机选择一个位置,如果结果不佳,我们将继续扔飞镖。
就像亚当说的那样,这在最坏的情况下可以永远持续下去,但从统计上讲,最坏的情况永远不会发生。:)
rand5
是统一的,则vals
网格中的每个单元都有被拾取的相等概率。网格在间隔[1,7]中正好包含每个整数的三个副本,外加四个零。因此,“原始”结果流趋向于均匀混合[1,7]值,加上一些零,其出现的时间比任何单个允许值更频繁。但这并不重要,因为去除了零,只剩下[1,7]值的均匀混合。
没有(完全正确)的解决方案可以在恒定的时间内运行,因为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迭代,但永远循环的可能性极小。
N
调用rand5()
。然后,对的调用序列有5 ^ N个可能的结果rand5
,每个输出结果为1-7。因此,如果将所有可能的呼叫序列k
相加,其输出每个1≤k≤7,则输出的概率k
为m / 5 ^ N,其中m是此类序列的数量。因此,m / 5 ^ N = 1/7,但是对于这个矛盾,没有可能的整数解(N,m)。
除了第一个答案外,我还要添加另一个答案。此答案尝试最大程度地减少对的rand5()
每次呼叫的呼叫次数rand7()
,以最大程度地利用随机性。也就是说,如果您认为随机性是一种宝贵的资源,我们希望在不丢弃任何随机位的情况下尽可能多地使用随机性。此答案也与Ivan答案中提出的逻辑有些相似之处。
随机变量的熵是一个定义明确的量。对于这需要在N个随机变量具有相等概率(均匀分布)状态,熵为log 2 N.因此,rand5()
具有熵的大约2.32193比特,并且rand7()
具有大约熵2.80735比特。如果我们希望最大程度地利用随机性,则需要使用每次对的熵的全部2.32193熵rand5()
,并将它们应用于生成对的每次调用所需的2.80735熵rand7()
。因此,基本限制是,对to的rand5()
每次调用,我们所能做的就是log(7)/ log(5)= 1.20906调用rand7()
。
旁注:除非另有说明,否则此答案中的所有对数均以2为底。 rand5()
假定返回数字[0,4],并且rand7()
假定返回数字[0,6]。将范围分别调整为[1,5]和[1,7]是微不足道的。
那么我们该怎么做呢?我们会生成一个介于0和1之间的无限精确的随机实数(假装我们可以实际计算并存储这样一个无限精确的数,稍后再解决)。我们可以通过在基体5产生其数字生成这样的数:我们挑选随机数0 a
1 a
2 a
3 ...,其中每个数字一个i
是通过向一个呼叫选择rand5()
。例如,如果我们的RNG i
为all 选择a = 1 i
,那么忽略了它不是非常随机的事实,它将对应于实数1/5 + 1/5 2 + 1/5 3 + ... = 1/4(一个几何序列的和)。
好的,所以我们选择了一个介于0和1之间的随机实数。我现在声称这样一个随机数是均匀分布的。直观地讲,这很容易理解,因为每个数字都是统一选取的,并且数字是无限精确的。但是,对此的正式证明要更多一些,因为现在我们处理的是连续分布而不是离散分布,因此我们需要证明我们的数字位于区间[ a
,b
] 中的概率等于该间隔b - a
。证明留给读者练习)。
现在,我们从[0,1]范围内均匀选择了一个随机实数,我们需要将其转换为[0,6]范围内的一系列均匀随机数以生成的输出rand7()
。我们如何做到这一点?与我们所做的恰好相反-我们将其转换为以7为底的无限精确的十进制,然后每个以7为底的数字将对应于的一个输出rand7()
。
以前面的示例为例,如果我们rand5()
产生1的无限流,那么我们的随机实数将为1/4。将1/4转换为基数7,我们得到无穷小数0.15151515 ...,因此我们将产生输出1、5、1、5、1、5等。
好的,所以我们有了主要思想,但是还有两个问题:我们实际上无法计算或存储无限精确的实数,那么如何只处理其中的有限部分呢?其次,我们如何实际将其转换为基数7?
我们可以将0到1之间的数字转换为以7为底的一种方法如下:
为了解决无限精度的问题,我们计算了部分结果,并且还存储了结果的上限。也就是说,假设我们调用了rand5()
两次,并且两次都返回了1。到目前为止,我们生成的数字为0.11(以5为底)。无论产生的无穷多个调用序列的其余部分是什么rand5()
,我们正在生成的随机实数永远不会大于0.12:0.11≤0.11xyz ... <0.12始终是事实。
因此,跟踪当前的数字以及它可能取得的最大值,我们将两个数字都转换为基数7。如果它们在前k
几个k
数字上都一致,那么我们就可以安全地输出下一个数字-不管数字是多少。以5为基数的无限流,它们将永远不会影响以k
7为基数的下一位数字!
这就是算法-生成的下一个输出rand7()
,我们只生成rand5()
所需数量的位数,以确保我们确定地知道在将随机实数转换为基数7时下一位的值。这是一个带有测试工具的Python实现:
import random
rand5_calls = 0
def rand5():
global rand5_calls
rand5_calls += 1
return random.randint(0, 4)
def rand7_gen():
state = 0
pow5 = 1
pow7 = 7
while True:
if state / pow5 == (state + pow7) / pow5:
result = state / pow5
state = (state - result * pow5) * 7
pow7 *= 7
yield result
else:
state = 5 * state + pow7 * rand5()
pow5 *= 5
if __name__ == '__main__':
r7 = rand7_gen()
N = 10000
x = list(next(r7) for i in range(N))
distr = [x.count(i) for i in range(7)]
expmean = N / 7.0
expstddev = math.sqrt(N * (1.0/7.0) * (6.0/7.0))
print '%d TRIALS' % N
print 'Expected mean: %.1f' % expmean
print 'Expected standard deviation: %.1f' % expstddev
print
print 'DISTRIBUTION:'
for i in range(7):
print '%d: %d (%+.3f stddevs)' % (i, distr[i], (distr[i] - expmean) / expstddev)
print
print 'Calls to rand5: %d (average of %f per call to rand7)' % (rand5_calls, float(rand5_calls) / N)
请注意,rand7_gen()
返回生成器,因为它的内部状态涉及将数字转换为基数7。测试工具调用next(r7)
10000次以生成10000个随机数,然后测量其分布。仅使用整数数学,因此结果完全正确。
还要注意,这里的数字变得非常大,非常快。5和7的幂快速增长。因此,由于使用bignum算法,在生成大量随机数后性能将开始显着下降。但是请记住,我的目标是最大化随机位的使用,而不是最大化性能(尽管这是次要目标)。
在一次运行中,我rand5()
对10000次调用进行了12091次调用,rand7()
平均将log(7)/ log(5)调用的最小值平均为4个有效数字,并且输出结果是均匀的。
为了将此代码移植到没有内置任何大整数的语言中,您必须将本机整数类型的值限制为最大值,pow5
并且pow7
将其限制为本机整数类型的最大值-如果它们太大,请重置一切,重新开始。这将使rand5()
每次呼叫的平均呼叫次数增加到rand7()
很小,但希望即使对于32位或64位整数也不应增加太多。
(我已经窃取了亚当·罗森菲尔德的答案,并使它的运行速度提高了约7%。)
假设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()
5 * rand5() + rand5()
。
算法:
7可以3比特的顺序表示
使用rand(5)用0或1随机填充每个位。
例如:调用rand(5)和
如果结果是1或2,
如果结果是4或5,则用0 填充该位;
如果结果是3,则用1填充该位,然后忽略并再次执行(拒绝)
这样,我们可以用0/1随机填充3位,从而得到1-7的数字。
编辑: 这似乎是最简单,最有效的答案,所以这里有一些代码:
public static int random_7() {
int returnValue = 0;
while (returnValue == 0) {
for (int i = 1; i <= 3; i++) {
returnValue = (returnValue << 1) + random_5_output_2();
}
}
return returnValue;
}
private static int random_5_output_2() {
while (true) {
int flip = random_5();
if (flip < 3) {
return 0;
}
else if (flip > 3) {
return 1;
}
}
}
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() = (rand5()+rand5()+rand5()+rand5()+rand5()+rand5()+rand5())%7+1
编辑:那不是很有效。千分之二的价格(假设是完美的rand5)就减少了2分。桶得到:
value Count Error%
1 11158 -0.0035
2 11144 -0.0214
3 11144 -0.0214
4 11158 -0.0035
5 11172 +0.0144
6 11177 +0.0208
7 11172 +0.0144
通过转换为
n Error%
10 +/- 1e-3,
12 +/- 1e-4,
14 +/- 1e-5,
16 +/- 1e-6,
...
28 +/- 3e-11
似乎每增加2个数量级
顺便说一句:上面的错误表不是通过抽样生成的,而是通过以下递归关系生成的:
p[x,n]
是调用时output=x
可以发生的方式数量。n
rand5
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}上产生均匀分布的随机数发生器在{1,2,3,4,5,6,7}上产生均匀分布。代码很杂乱,但是逻辑很清楚。
public static int random_7(Random rg) {
int returnValue = 0;
while (returnValue == 0) {
for (int i = 1; i <= 3; i++) {
returnValue = (returnValue << 1) + SimulateFairCoin(rg);
}
}
return returnValue;
}
private static int SimulateFairCoin(Random rg) {
while (true) {
int flipOne = random_5_mod_2(rg);
int flipTwo = random_5_mod_2(rg);
if (flipOne == 0 && flipTwo == 1) {
return 0;
}
else if (flipOne == 1 && flipTwo == 0) {
return 1;
}
}
}
private static int random_5_mod_2(Random rg) {
return random_5(rg) % 2;
}
private static int random_5(Random rg) {
return rg.Next(5) + 1;
}
int rand7() {
int value = rand5()
+ rand5() * 2
+ rand5() * 3
+ rand5() * 4
+ rand5() * 5
+ rand5() * 6;
return value%7;
}
与选择的解决方案不同,该算法将在恒定时间内运行。但是,它对rand5的调用确实比所选解决方案的平均运行时间多2次。
请注意,此生成器不是完美的(数字0比其他任何数字都有0.0064%的机会),但是对于大多数实际目的而言,保证恒定时间可能会超过此误差。
说明
该解决方案源于数字15,624可被7整除的事实,因此,如果我们可以随机且均匀地生成0到15,624之间的数字,然后采用mod 7,我们可以获得近似均匀的rand7生成器。通过将rand5滚动6次并使用它们形成以5为基数的数字,可以统一生成0到15624之间的数字:
rand5 * 5^5 + rand5 * 5^4 + rand5 * 5^3 + rand5 * 5^2 + rand5 * 5 + rand5
但是,mod 7的属性使我们可以简化方程式:
5^5 = 3 mod 7
5^4 = 2 mod 7
5^3 = 6 mod 7
5^2 = 4 mod 7
5^1 = 5 mod 7
所以
rand5 * 5^5 + rand5 * 5^4 + rand5 * 5^3 + rand5 * 5^2 + rand5 * 5 + rand5
变成
rand5 * 3 + rand5 * 2 + rand5 * 6 + rand5 * 4 + rand5 * 5 + rand5
理论
15,624不是随机选择的,而是可以使用费马小定理发现的,该定理指出如果p是素数,则
a^(p-1) = 1 mod p
所以这给了我们
(5^6)-1 = 0 mod 7
(5 ^ 6)-1等于
4 * 5^5 + 4 * 5^4 + 4 * 5^3 + 4 * 5^2 + 4 * 5 + 4
这是一个以5为基数的数字,因此我们可以看到,该方法可用于从任何随机数生成器转换为任何其他随机数生成器。尽管在使用指数p-1时总是会向0产生小的偏差。
为了概括这种方法并使其更加准确,我们可以使用如下函数:
def getRandomconverted(frm, to):
s = 0
for i in range(to):
s += getRandomUniform(frm)*frm**i
mx = 0
for i in range(to):
mx = (to-1)*frm**i
mx = int(mx/to)*to # maximum value till which we can take mod
if s < mx:
return s%to
else:
return getRandomconverted(frm, to)
这里允许做作业吗?
此函数执行粗略的“以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;
}
如果我们考虑尝试给出最有效答案的附加约束,即给定一个输入流,则I
长度m
为1-5 的均匀分布整数的输出流O
,相对于最长长度的1-7的均匀分布整数的流对m
,说L(m)
。
分析此问题的最简单方法是将流I和I分别O
视为5元和7元数。这是通过主要答案的想法来实现的,并且对stream a1, a2, a3,... -> a1+5*a2+5^2*a3+..
同样如此O
。
然后,如果我们截取一部分输入流,长度为m choose n s.t. 5^m-7^n=c
,c>0
则其中和尽可能小。再有就是从长度为m为整数输入流的均匀映射从1
到5^m
和从整数到另一均匀地图从1 7^n
到长度的输出流n,其中我们可能必须从输入流失去一些情况下当所述映射整数超过7^n
。
因此,这给出了一个值L(m)
围绕m (log5/log7)
这大约是.82m
。
上述分析的难点在于方程式难以5^m-7^n=c
精确求解,以及从1
到的统一值5^m
超过7^n
而失去效率的情况。
问题是,如何才能达到m的最佳可能值(log5 / log7)。例如,当这个数字接近整数时,我们可以找到一种方法来实现输出值的这个精确整数吗?
如果5^m-7^n=c
从输入流中提取了有效值,则我们将有效地从生成一个统一的随机数0
,(5^m)-1
并且不使用任何大于的值7^n
。但是,这些值可以挽救并再次使用。它们有效地生成从1到的统一数字序列5^m-7^n
。因此,我们可以尝试使用它们并将它们转换为7进制数,以便我们可以创建更多输出值。
如果我们T7(X)
将random(1-7)
整数的输出序列的平均长度设为均匀大小输入X
,并假定为5^m=7^n0+7^n1+7^n2+...+7^nr+s, s<7
。
然后,T7(5^m)=n0x7^n0/5^m + ((5^m-7^n0)/5^m) T7(5^m-7^n0)
由于我们有一个长度为no的序列,概率为7 ^ n0 / 5 ^ m,剩余的序列的5^m-7^n0
概率为(5^m-7^n0)/5^m)
。
如果我们继续替换,我们将获得:
T7(5^m) = n0x7^n0/5^m + n1x7^n1/5^m + ... + nrx7^nr/5^m = (n0x7^n0 + n1x7^n1 + ... + nrx7^nr)/5^m
因此
L(m)=T7(5^m)=(n0x7^n0 + n1x7^n1 + ... + nrx7^nr)/(7^n0+7^n1+7^n2+...+7^nr+s)
另一种放置方式是:
If 5^m has 7-ary representation `a0+a1*7 + a2*7^2 + a3*7^3+...+ar*7^r
Then L(m) = (a1*7 + 2a2*7^2 + 3a3*7^3+...+rar*7^r)/(a0+a1*7 + a2*7^2 + a3*7^3+...+ar*7^r)
最好的情况是我原来在哪里5^m=7^n+s
,哪里s<7
。
然后T7(5^m) = nx(7^n)/(7^n+s) = n+o(1) = m (Log5/Log7)+o(1)
像以前一样。
最坏的情况是我们只能找到k和st 5 ^ m = kx7 + s。
Then T7(5^m) = 1x(k.7)/(k.7+s) = 1+o(1)
其他情况介于两者之间。看到我们对于非常大的m可以做的很好,即我们能得到多大的误差项,将会很有趣:
T7(5^m) = m (Log5/Log7)+e(m)
e(m) = o(1)
总体看来,这似乎是不可能的,但希望我们能证明e(m)=o(m)
。
然后,整个过程取决于的5^m
各种值的7进制数字的分布m
。
我敢肯定,有很多理论可以解决这个问题,我可能会看一下,并在某些时候进行汇报。
这是Adam的answer的有效Python实现。
import random
def rand5():
return random.randint(1, 5)
def rand7():
while True:
r = 5 * (rand5() - 1) + rand5()
#r is now uniformly random between 1 and 25
if (r <= 21):
break
#result is now uniformly random between 1 and 7
return r % 7 + 1
我喜欢将我正在查看的算法投入Python,以便可以与它们一起玩,以为我希望将其发布在这里,希望它对外面的人有用,而不是花很长时间一起投入。
假设rand(n)的 意思是“从0到n-1均匀分布的随机整数”,这是一个使用Python randrand的代码示例,具有这种效果。它仅使用randint(5)和常量来产生randint(7)的效果。有点傻,实际上
from random import randint
sum = 7
while sum >= 7:
first = randint(0,5)
toadd = 9999
while toadd>1:
toadd = randint(0,5)
if toadd:
sum = first+5
else:
sum = first
assert 7>sum>=0
print sum
do ... while
。它可能是1337
,或12345
,或任何数量的> 1
Adam Rosenfield正确答案的前提是:
当n等于2时,您有4种扔掉的可能性:y = {22,23,24,25}。如果您使用n等于6,则只有1个丢球:y = {15625}。
5 ^ 6 = 15625
= 15625 7 * 2232 = 15624
您再拨打rand5次。但是,获得扔掉值(或无限循环)的机会要低得多。如果有一种方法无法获得y的抛弃值,则尚未找到。
这是我的答案:
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的调用。与其他解决方案一样,它可以长时间循环的可能性很小。
简单高效:
int rand7 ( void )
{
return 4; // this number has been calculated using
// rand5() and is in the range 1..7
}
(灵感来自您最喜欢的“程序员”动画片是什么?)。
只要没有7种可能性可供选择,请绘制另一个随机数,该随机数会将可能性数乘以5。在Perl中:
$num = 0;
$possibilities = 1;
sub rand7
{
while( $possibilities < 7 )
{
$num = $num * 5 + int(rand(5));
$possibilities *= 5;
}
my $result = $num % 7;
$num = int( $num / 7 );
$possibilities /= 7;
return $result;
}
$possibilities
必须始终增长到25才能退出循环并返回。因此,您的第一个结果是[0-124] % 7
,由于125 % 7 != 0
(实际上是6),它不是均匀分布的。
我不喜欢从1开始的范围,所以我将从0开始:-)
unsigned rand5()
{
return rand() % 5;
}
unsigned rand7()
{
int r;
do
{
r = rand5();
r = r * 5 + rand5();
r = r * 5 + rand5();
r = r * 5 + rand5();
r = r * 5 + rand5();
r = r * 5 + rand5();
} while (r > 15623);
return r / 2232;
}
from collections import defaultdict def r7(n): if not n: yield [] else: for i in range(1, 6): for j in r7(n-1): yield [i] + j def test_r7(): d = defaultdict(int) for x in r7(6): s = (((((((((x[5] * 5) + x[4]) * 5) + x[3]) * 5) + x[2]) * 5) + x[1]) * 5) + x[0] if s <= 15623: d[s % 7] += 1 print d
我知道已经回答了,但这似乎行得通,但是我不能告诉你它是否有偏差。我的“测试”表明至少是合理的。
也许亚当·罗森菲尔德会好心地发表评论?
我的想法(天真?)是这样的:
累积rand5直到有足够的随机位来生成rand7。最多需要2 rand5。要获得rand7数字,我使用累计值mod 7。
为了避免累加器溢出,并且由于累加器为mod 7,所以我采用累加器的mod 7:
(5a + rand5) % 7 = (k*7 + (5a%7) + rand5) % 7 = ( (5a%7) + rand5) % 7
rand7()函数如下:
(我让rand5的范围为0-4,rand7同样为0-6。)
int rand7(){
static int a=0;
static int e=0;
int r;
a = a * 5 + rand5();
e = e + 5; // added 5/7ths of a rand7 number
if ( e<7 ){
a = a * 5 + rand5();
e = e + 5; // another 5/7ths
}
r = a % 7;
e = e - 7; // removed a rand7 number
a = a % 7;
return r;
}
编辑:增加了1亿次试验的结果。
'Real'rand函数mod 5或7
rand5:avg = 1.999802 0:20003944 1:19999889 2:20003690 3:19996938 4:19995539 rand7:avg = 3.000111 0:14282851 1:14282879 2:14284554 3:14288546 4:14292388 5:14288736 6:14280046
我的rand7
平均看起来还可以,数字分布也可以。
兰特:avg = 3.000080 0:14288793 1:14280135 2:14287848 3:14285277 4:14286341 5:14278663 6:14292943
上面引用了一些精美的算法,但这是一种处理方法,尽管它可能是回旋处。我假设从0生成的值。
R2 =给出小于2的值的随机数生成器(样本空间= {0,1})
R8 =给出小于8的值的随机数生成器(样本空间= {0,1,2,3,4,5,5,6,7 })
为了从R2生成R8,您将运行R2三次,并将所有3次运行的组合结果用作具有3位数字的二进制数。这是R2运行三次时的值范围:
0 0 0-> 0
。
。
1 1 1-> 7
现在要从R8生成R7,如果R7返回7,我们只需再次运行R7:
int R7() {
do {
x = R8();
} while (x > 6)
return x;
}
环形交叉路口解决方案是从R5生成R2(就像我们从R8生成R7一样),然后从R2生成R8,然后从R8生成R7。
这是一个完全适合整数且在最佳值的4%以内的解决方案(即,{0..6}中的每个数字在{0..4}中使用1.26个随机数)。代码在Scala中,但是数学在任何语言中都应该相当清楚:您利用7 ^ 9 + 7 ^ 8非常接近5 ^ 11的事实。因此,您选择以5为底的11位数字,然后将其解释为以7为底的9位数字(如果它在范围内)(给出9个以7为底的数字),或者解释为8位数字(如果超过9位数字),等等。 ::
abstract class RNG {
def apply(): Int
}
class Random5 extends RNG {
val rng = new scala.util.Random
var count = 0
def apply() = { count += 1 ; rng.nextInt(5) }
}
class FiveSevener(five: RNG) {
val sevens = new Array[Int](9)
var nsevens = 0
val to9 = 40353607;
val to8 = 5764801;
val to7 = 823543;
def loadSevens(value: Int, count: Int) {
nsevens = 0;
var remaining = value;
while (nsevens < count) {
sevens(nsevens) = remaining % 7
remaining /= 7
nsevens += 1
}
}
def loadSevens {
var fivepow11 = 0;
var i=0
while (i<11) { i+=1 ; fivepow11 = five() + fivepow11*5 }
if (fivepow11 < to9) { loadSevens(fivepow11 , 9) ; return }
fivepow11 -= to9
if (fivepow11 < to8) { loadSevens(fivepow11 , 8) ; return }
fivepow11 -= to8
if (fivepow11 < 3*to7) loadSevens(fivepow11 % to7 , 7)
else loadSevens
}
def apply() = {
if (nsevens==0) loadSevens
nsevens -= 1
sevens(nsevens)
}
}
如果将测试粘贴到解释器中(实际上是REPL),则会得到:
scala> val five = new Random5
five: Random5 = Random5@e9c592
scala> val seven = new FiveSevener(five)
seven: FiveSevener = FiveSevener@143c423
scala> val counts = new Array[Int](7)
counts: Array[Int] = Array(0, 0, 0, 0, 0, 0, 0)
scala> var i=0 ; while (i < 100000000) { counts( seven() ) += 1 ; i += 1 }
i: Int = 100000000
scala> counts
res0: Array[Int] = Array(14280662, 14293012, 14281286, 14284836, 14287188,
14289332, 14283684)
scala> five.count
res1: Int = 125902876
分布良好且平坦(每个仓中大约有10k的10/8的1/7的10k左右,这是近似高斯分布所期望的)。
通过使用滚动总计,您可以
这两个问题都是简单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)
这个答案更多是通过Rand5函数获得最大熵的实验。因此,t尚不清楚,几乎可以肯定比其他实现要慢得多。
假设0-4的均匀分布和0-6的均匀分布:
public class SevenFromFive
{
public SevenFromFive()
{
// this outputs a uniform ditribution but for some reason including it
// screws up the output distribution
// open question Why?
this.fifth = new ProbabilityCondensor(5, b => {});
this.eigth = new ProbabilityCondensor(8, AddEntropy);
}
private static Random r = new Random();
private static uint Rand5()
{
return (uint)r.Next(0,5);
}
private class ProbabilityCondensor
{
private readonly int samples;
private int counter;
private int store;
private readonly Action<bool> output;
public ProbabilityCondensor(int chanceOfTrueReciprocal,
Action<bool> output)
{
this.output = output;
this.samples = chanceOfTrueReciprocal - 1;
}
public void Add(bool bit)
{
this.counter++;
if (bit)
this.store++;
if (counter == samples)
{
bool? e;
if (store == 0)
e = false;
else if (store == 1)
e = true;
else
e = null;// discard for now
counter = 0;
store = 0;
if (e.HasValue)
output(e.Value);
}
}
}
ulong buffer = 0;
const ulong Mask = 7UL;
int bitsAvail = 0;
private readonly ProbabilityCondensor fifth;
private readonly ProbabilityCondensor eigth;
private void AddEntropy(bool bit)
{
buffer <<= 1;
if (bit)
buffer |= 1;
bitsAvail++;
}
private void AddTwoBitsEntropy(uint u)
{
buffer <<= 2;
buffer |= (u & 3UL);
bitsAvail += 2;
}
public uint Rand7()
{
uint selection;
do
{
while (bitsAvail < 3)
{
var x = Rand5();
if (x < 4)
{
// put the two low order bits straight in
AddTwoBitsEntropy(x);
fifth.Add(false);
}
else
{
fifth.Add(true);
}
}
// read 3 bits
selection = (uint)((buffer & Mask));
bitsAvail -= 3;
buffer >>= 3;
if (selection == 7)
eigth.Add(true);
else
eigth.Add(false);
}
while (selection == 7);
return selection;
}
}
每次对Rand5的调用添加到缓冲区的位数为4/5 * 2,因此为1.6。如果包含的1/5概率值增加了0.05,则增加了1.65,但请参阅代码中的注释,我必须禁用此注释。
调用Rand7消耗的位= 3 + 1/8 *(3 + 1/8 *(3 + 1/8 *(...
这是3 + 3/8 + 3/64 + 3/512 ...约3.42
通过从七位数中提取信息,我每次调用可以回收1/8 * 1/7位,因此约为0.018
这样,每个调用的净消耗为3.4位,这意味着每个Rand7的Rand5调用比率为2.125。最佳值为2.1。
我猜想这种做法是显著慢于这里的许多其他的人,除非调用Rand5的成本是非常昂贵的(说呼唤熵的一些外部源)。
在PHP中
function rand1to7() {
do {
$output_value = 0;
for ($i = 0; $i < 28; $i++) {
$output_value += rand1to5();
}
while ($output_value != 140);
$output_value -= 12;
return floor($output_value / 16);
}
循环产生一个介于16和127之间的随机数,除以16以创建一个介于1和7.9375之间的浮点数,然后向下舍入以获得一个介于1和7之间的整数。如果我没记错的话,有16/112的机会7个结果中的任何一个。
extern int r5();
int r7() {
return ((r5() & 0x01) << 2 ) | ((r5() & 0x01) << 1 ) | (r5() & 0x01);
}
7 = 111b
与p(7) = 8 / 125
我想我有四个答案,两个给出了像@Adam Rosenfield一样的精确解决方案,但是没有无限循环问题,另外两个给出了几乎完美的解决方案,但是比第一个实现起来更快。
最佳的精确解决方案需要7个调用rand5
,但为了理解,请继续进行。
亚当的回答的强项是它给出了一个完美的均匀分布,并且极有可能(21/25)仅需要两次调用rand5()。但是,最坏的情况是无限循环。
下面的第一个解决方案还提供了一个完美的均匀分布,但是总共需要42次调用rand5
。没有无限循环。
这是一个R实现:
rand5 <- function() sample(1:5,1)
rand7 <- function() (sum(sapply(0:6, function(i) i + rand5() + rand5()*2 + rand5()*3 + rand5()*4 + rand5()*5 + rand5()*6)) %% 7) + 1
对于不熟悉R的人,这里是简化版本:
rand7 = function(){
r = 0
for(i in 0:6){
r = r + i + rand5() + rand5()*2 + rand5()*3 + rand5()*4 + rand5()*5 + rand5()*6
}
return r %% 7 + 1
}
的分配rand5
将保留。如果进行数学运算,则循环的7个迭代中的每一个都有5 ^ 6个可能的组合,因此,可能的组合的总数为(7 * 5^6) %% 7 = 0
。因此,我们可以将生成的随机数划分为7个相等的组。有关此的更多讨论,请参见方法2。
以下是所有可能的组合:
table(apply(expand.grid(c(outer(1:5,0:6,"+")),(1:5)*2,(1:5)*3,(1:5)*4,(1:5)*5,(1:5)*6),1,sum) %% 7 + 1)
1 2 3 4 5 6 7
15625 15625 15625 15625 15625 15625 15625
我认为直接表明亚当的方法将运行得快得多。rand5
亚当的解决方案中有42个或更多调用的可能性非常小((4/25)^21 ~ 10^(-17)
)。
现在,第二种方法几乎是统一的,但需要6次调用rand5
:
rand7 <- function() (sum(sapply(1:6,function(i) i*rand5())) %% 7) + 1
这是一个简化的版本:
rand7 = function(){
r = 0
for(i in 1:6){
r = r + i*rand5()
}
return r %% 7 + 1
}
这本质上是方法1的一次迭代。如果我们生成所有可能的组合,则结果为:
table(apply(expand.grid(1:5,(1:5)*2,(1:5)*3,(1:5)*4,(1:5)*5,(1:5)*6),1,sum) %% 7 + 1)
1 2 3 4 5 6 7
2233 2232 2232 2232 2232 2232 2232
一个数字将在5^6 = 15625
审判中再次出现。
现在,在方法1中,通过将1加到6,我们将数字2233移动到每个连续点。因此,组合的总数将匹配。之所以可行,是因为5 ^ 6 %% 7 = 1,然后我们做了7个适当的变化,所以(7 * 5 ^ 6 %% 7 = 0)。
如果可以理解方法1和2的参数,则方法3紧随其后,并且仅需要7次调用rand5
。在这一点上,我认为这是一个精确解决方案所需的最少呼叫次数。
这是一个R实现:
rand5 <- function() sample(1:5,1)
rand7 <- function() (sum(sapply(1:7, function(i) i * rand5())) %% 7) + 1
对于不熟悉R的人,这里是简化版本:
rand7 = function(){
r = 0
for(i in 1:7){
r = r + i * rand5()
}
return r %% 7 + 1
}
的分配rand5
将保留。如果我们做数学运算,则循环的7个迭代中的每一个都有5种可能的结果,因此,可能的组合总数为(7 * 5) %% 7 = 0
。因此,我们可以将生成的随机数划分为7个相等的组。有关此的更多讨论,请参见方法一和方法二。
以下是所有可能的组合:
table(apply(expand.grid(0:6,(1:5)),1,sum) %% 7 + 1)
1 2 3 4 5 6 7
5 5 5 5 5 5 5
我认为可以直接证明亚当的方法仍将运行得更快。rand5
亚当的解决方案中有7个或更多呼叫的可能性仍然很小((4/25)^3 ~ 0.004
)。
这是第二种方法的微小变化。它几乎是统一的,但是需要7次调用rand5
,这是方法2的另外一个:
rand7 <- function() (rand5() + sum(sapply(1:6,function(i) i*rand5())) %% 7) + 1
这是一个简化的版本:
rand7 = function(){
r = 0
for(i in 1:6){
r = r + i*rand5()
}
return (r+rand5()) %% 7 + 1
}
如果我们生成所有可能的组合,则结果为:
table(apply(expand.grid(1:5,(1:5)*2,(1:5)*3,(1:5)*4,(1:5)*5,(1:5)*6,1:5),1,sum) %% 7 + 1)
1 2 3 4 5 6 7
11160 11161 11161 11161 11161 11161 11160
在5^7 = 78125
试验中,两个数字将减少一次。对于大多数目的,我可以接受。
i=7
也没有任何影响,因为加入7*rand5()
到r
不改变的值r
MOD 7)
您需要的功能是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