已知rand_N()可以均匀生成[1,N]间的随机数,则(rand_X() - 1) * Y + rand_Y()可以均匀生成[1,X * Y ]间的随机数,即实现了rand_XY()。
如下所示,用如下代码可生成[1,63]间所有整数数据:
for i in range(0,7):
for j in range(1,10):
print(i * 9 + j, end=' ')
print()
所得数据如下:
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
即可用(rand_7() - 1) * 9 + rand_9()表示rand_63()。
则从rand_XY()取得rand_X()即可通过以下方法:rand_X() = rand_XY() % X + 1来实现。
以上均为原理部分,接下来为实际操作部分,原题如下:
给定方法 rand7 可生成 [1,7] 范围内的均匀随机整数,试写一个方法 rand10 生成 [1,10] 范围内的均匀随机整数。
你只能调用 rand7() 且不能调用其他方法。请不要使用系统的 Math.random() 方法。
每个测试用例将有一个内部参数 n,即你实现的函数 rand10() 在测试时将被调用的次数。请注意,这不是传递给 rand10() 的参数。
首先将rand7 范围扩展到>=10,利用(rand_7() - 1) * 7 + rand_7()拓展到rand_49()。这时发现该数不是10的倍数,此时利用"拒绝采样"的知识,将大于40的数据全部拒绝,仅保留小于等于40的数据,得到rand_10() = rand_40() % 10 + 1。1-10间所有数据取到的可能均为4/49。
代码如下:
def rand_10():
while(True):
res = (rand7() - 1) * 7 + rand7()
if res <= 40:
return res % 10 + 1
可以对该代码进行优化,即将拒绝的数据重复使用上面的方法,减少拒绝的数据,下面是改进代码:
def rand_10():
while(True):
res = (rand7() - 1) * 7 + rand7()
if res <= 40:
return res % 10 + 1
res = (res - 40 - 1) * 7 + rand7()
if res <= 60:
return res % 10 + 1
res = (res - 60 - 1) * 7 + rand7()
if res <= 20:
return res % 10 + 1
下面是另一种方法:即利用两次rand_7()取得1/2和1/5的概率,将两者作乘法即可得到1/10的概率:
def rand_10():
first, second = 0, 0
while (first := rand7()) > 6:
pass
while (second := rand7()) >5:
pass
return second if first & 1 == 1 else 5 + second
当然,若要取得类似rand_11()则可取得1/2和1/6的概率,并将数据12拒绝即可。