给定一个函数,它产生的是1到5之间的随机整数,写一个函数,它产生的是1到7之间的随机整数。
当前回答
这个解决方案不浪费任何熵,并给出了范围内第一个可用的真正随机数。随着每一次迭代,得不到答案的概率可证明地降低了。在N次迭代中得到答案的概率是0到max (5^N)之间的随机数小于该范围内7的最大倍数(max-max%7)的概率。必须迭代至少两次。但这对所有解都是成立的。
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;
}
}
数值上等价于:
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;
}
第一个代码以模形式计算。第二个代码只是简单的数学。或者我在某个地方犯了错误。: -)
其他回答
下面是Adam回答的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,这样我就可以摆弄它们,我想我把它贴在这里,希望它对外面的人有用,而不是花很长时间来拼凑。
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的数字,然后对7取余,我们就可以得到一个近乎均匀的rand7生成器。将rand5滚动6次,将0到15624之间的数字统一生成,并使用这些数字组成以5为基数的数字,如下所示:
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
So
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
理论
15624这个数字不是随机选择的,而是可以用费马小定理来发现的,该定理指出,如果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)
除了我的第一个答案,我想再补充一个答案。这个答案试图最小化每次调用rand7()时对rand5()的调用次数,以最大限度地利用随机性。也就是说,如果你认为随机性是一种宝贵的资源,我们就会尽可能多地利用它,而不丢弃任何随机比特。这个答案也与伊万的回答中的逻辑有一些相似之处。
The entropy of a random variable is a well-defined quantity. For a random variable which takes on N states with equal probabilities (a uniform distribution), the entropy is log2 N. Thus, rand5() has approximately 2.32193 bits of entropy, and rand7() has about 2.80735 bits of entropy. If we hope to maximize our use of randomness, we need to use all 2.32193 bits of entropy from each call to rand5(), and apply them to generating 2.80735 bits of entropy needed for each call to rand7(). The fundamental limit, then, is that we can do no better than log(7)/log(5) = 1.20906 calls to rand5() per call to rand7().
附注:除非另有说明,否则此答案中的所有对数都将以2为底。Rand5()将被假定为返回范围[0,4]的数字,rand7()将被假定为返回范围[0,6]的数字。分别将范围调整为[1,5]和[1,7]是很简单的。
So how do we do it? We generate an infinitely precise random real number between 0 and 1 (pretend for the moment that we could actually compute and store such an infinitely precise number -- we'll fix this later). We can generate such a number by generating its digits in base 5: we pick the random number 0.a1a2a3..., where each digit ai is chosen by a call to rand5(). For example, if our RNG chose ai = 1 for all i, then ignoring the fact that that isn't very random, that would correspond to the real number 1/5 + 1/52 + 1/53 + ... = 1/4 (sum of a geometric series).
Ok, so we've picked a random real number between 0 and 1. I now claim that such a random number is uniformly distributed. Intuitively, this is easy to understand, since each digit was picked uniformly, and the number is infinitely precise. However, a formal proof of this is somewhat more involved, since now we're dealing with a continuous distribution instead of a discrete distribution, so we need to prove that the probability that our number lies in an interval [a, b] equals the length of that interval, b - a. The proof is left as an exercise for the reader =).
现在我们有一个从范围[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为底的一种方法如下:
乘以7 结果的积分部分是下一个以7为基数的数字 减去积分部分,只留下小数部分 转到第一步
为了处理无限精度的问题,我们计算一个部分结果,并存储结果的上界。也就是说,假设我们调用rand5()两次,两次都返回1。到目前为止,我们生成的数字是0.11(以5为基数)。无论rand5()调用的无限序列的剩余部分产生什么,我们生成的随机实数永远不会大于0.12:0.11≤0.11xyz…< 0.12。
因此,跟踪当前数字到目前为止,以及它可能的最大值,我们将两个数字都转换为以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的幂增长很快。因此,在生成大量随机数后,由于大算术,性能将开始明显下降。但请记住,我的目标是最大化随机位的使用,而不是最大化性能(尽管这是次要目标)。
在一次运行中,我对rand5()进行了12091次调用,对rand7()进行了10000次调用,实现了log(7)/log(5)次调用的最小值,平均为4位有效数字,结果输出是均匀的。
为了将这段代码移植到一种没有内置任意大整数的语言中,您必须将pow5和pow7的值限制为本地整型类型的最大值——如果它们变得太大,则重置所有内容并重新开始。这将使每次调用rand7()时对rand5()的平均调用次数略有增加,但希望即使对于32或64位整数也不会增加太多。
extern int r5();
int r7() {
return ((r5() & 0x01) << 2 ) | ((r5() & 0x01) << 1 ) | (r5() & 0x01);
}
(我剽窃了亚当·罗森菲尔德的答案,使其运行速度提高了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()来生成一个新的数字和一个新的max。然后我们重新开始。
编辑:在这个方程中,期望调用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()