给你一个能生成随机整数1-7的函数,就叫他生成器get7吧,用它来生成一个1-11的随机整数,不能使用random,并且要等几率。javascript
getx就是指一个能生成1到x的随机数的函数java
主角:get7(大家全部人都没有random这个技能,全都disable了)数组
function get7() {
return ~~(Math.random()*7)+1 //规则:整篇文章,惟一能用random的地方
}
复制代码
既然是扩展,那么我给小范围随机数生成器扩展个几倍,再截取目标随机数范围不就得了。 先问一下,怎么用get7能实现一个合格的get14?这样子?bash
function get14(){
return get7() +get7()
}
复制代码
咱们来测试一下:dom
var obj = Object.create(null)
for(var i = 0;i<1000000;i++){
var n = get14();
if(!obj[n]){
obj[n] = 1
}else{
obj[n] ++
}
}
console.log(obj)
复制代码
先不说最小值的问题,首先,他不是等几率的序列 那么咱们探讨另外一个问题,究竟get14能不能直接用get7加减乘除就表示出来?函数
喂,说get7() 乘以11/7的那个,你肯定没问题?性能
既然是小范围随机扩展到大范围,那么确定离不开小范围随机数生成器get7的屡次调用。固然咱们最终目标很明确,目标随机数生成器get11,它的每个随机数都会等几率映射到get7的扩展序列里面:测试
而后咱们很快就能够想到一个公式:ui
a*(getx - 1) + getx
复制代码
a是个整数,整个公式含义是,把getx扩展为a倍,而且实现等几率分布。好比x是3,咱们想扩展成2倍,a=2,就是2*(get3 - 1) + get3,范围是1-7,可是,咱们看看几率:spa
x\y 1 2 3
0 1 2 3
2 3 4 5
4 5 6 7 =》1-7的几率是1:1:2:1:2:1:1
复制代码
明显这个公式还有前提
咱们再看a = 1:
x\y 1 2 3
0 1 2 3
1 2 3 4
2 3 4 5 =》1-5的几率是1:2:3:2:1
复制代码
好像矩阵每一行都是有交集
//若是a是3,ran3 - 1生成0-6 ,ran3 生成 1-3
x\y 1 2 3
0 1 2 3
3 4 5 6
6 7 8 9 =》1-9等几率
//若是a是4,ran3 - 1生成0-8 ,ran3 生成 1-3
x\y 1 2 3
0 1 2 3
4 5 6 7
8 9 10 11 =》数字都是等几率出现的,只是中间缺失了一些数,但不影响大局
复制代码
因此,只要保证全部的数等几率出现,先知足映射表最大值大于等于自身的平方(3*3 = 9,即a至少要是3) 为何呢?由于不足自己,必然有交集,就像上面1和2两个矩阵每一行都有交集,只要大于自己的大小,矩阵每一行就不会有交集,没有交集,那它们就能够等几率 因此,对于7想扩展一个等几率序列,get14(get小于49都是没用,不是等几率)显然是没用的,起码要get49,此后全部的序列长度都是49。因此一个get14得经过get49获得,咱们也能够从get49到get11了
function get49(){
var n = 7*(get7()-1) + get7() //a*(getx - 1) + getx,a取7,不信本身打印看一下
return n
}
var obj = Object.create(null)
for(var i = 0;i<1000000;i++){
var n = get49();
if(!obj[n]){
obj[n] = 1
}else{
obj[n] ++
}
}
console.log(obj)
复制代码
既然咱们看见所有元素等几率出现了,那咱们只要把多余的排除掉就行,便是遇到不是咱们想要的范围那些,从新生成一次。
function get11(){
var n = 7*(get7()-1) + get7() //a*(getx - 1) + getx,a取7,不信本身打印看一下
return n > 11?get11():n
}
复制代码
改改get49就行,对了,好像扩展数组太多没用的了,咱们应该提升扩展数组的利用率,而且减少递归次数
function get11(){
var n = 7*(get7()-1) + get7()
return n>44?get11():~~((n-1) / 4)+1
}
复制代码
对小随机数函数进行二进制划分,一半表示1一半表示0,而后用二进制表示大随机数,再去除多余的 get7到get11,8<11<16,咱们取4位二进制,也就是取4次get7 由于7是奇数,咱们就去掉一个吧,那咱们去掉1,当遇到1从新生成一次,剩下的划分二等分
//获取二进制序列
function getBinary(){
var n = get7()
if(n>4){
return 1
}else if(n>1){
return 0
}else{
return getBinary()
}
}
//二进制序列转化回去
function get11_by_binary(){
var res = ''
for(var i = 0;i<4;i++){
res += getBinary()
}
res = parseInt(res,2)
return res>11?get11_by_binary():res
}
复制代码
固然,性能会差不少,由于太多遍历了。通用版本也不难,能够本身封装一个。
其实第一种方法叫作拒绝采样。咱们知道等几率生成某个范围的随机数,想经过这个函数生成一个更小范围的随机数,就应该这样子:超过预期范围,从新抽取,因此叫作拒绝采样。 基本的操做:
//咱们仍是用get7获取1到小于7的随机数
function getn(n){//n是小于7的正整数
var num = get7()
return num > n?getn(n):num
}
//while形式
function getn(n){
var t
do{
t = get7()
}while(t>n)
return t
}
复制代码
那咱们get14就能够很灵活得到了,7*2获得14是吧,那就来:
function get14(){
var t
do{
t = get7()
}while(t>2)//咱们就叫他get2吧
return get7() + 7 * (t -1) //上面的a*(getx - 1) + getx公式的变种,这个a等于7
}
复制代码
都get14了,那get11还会远吗,大于11就拒绝采样咯。前面说先要get49?这只是一个按部就班的过程,这样子你能够深入理解到这个过程要怎么来,是否是感受拒绝采样很灵活?
公式推广: 已知生成器getn能生成1-n的随机数,那么由getn拒绝采样获得的新生成器geta和getb(a,b都不大于n),能够生成get(a*b):
get(a*b) = geta + a*(getb-1)//公式是对称的,能够交换a和b
//上面的例子用公式解释
get14() = get7() + 7 * (get2() -1) = get2() + 2*(get7() -1)
//其实get10也能够
get10() = get5() + 5 * (get2() -1) = get2() + 2*(get5() -1)
复制代码
get7能得到get2,那get14就能够获得了还能够得到get5,那get10也是能够作到。刚恰好就是最完美的,若是目标生成器是质数,就让拒绝采样次数尽可能少,也就是尽可能靠近目标。这种随机数扩展, 套路就是超过的拒绝采样,不足的利用加法和乘法使得刚恰好到目标范围或者超过目标