js隨機數生成器的擴展

0.前言

給你一個能生成隨機整數1-7的函數,就叫他生成器get7吧,用它來生成一個1-11的隨機整數,不能使用random,並且要等機率。javascript

getx就是指一個能生成1到x的隨機數的函數java

主角:get7(大家全部人都沒有random這個技能,全都disable了)數組

function get7() {
	return ~~(Math.random()*7)+1 //規則:整篇文章,惟一能用random的地方
}
複製代碼

1.擴展+分區

既然是擴展,那麼我給小範圍隨機數生成器擴展個幾倍,再截取目標隨機數範圍不就得了。 先問一下,怎麼用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的那個,你肯定沒問題?性能

1.1 擴展

既然是小範圍隨機擴展到大範圍,那麼確定離不開小範圍隨機數生成器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
複製代碼

明顯這個公式還有前提

1.2 a取值範圍

咱們再看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了

1.3 從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
}
複製代碼

2.二進制法

對小隨機數函數進行二進制劃分,一半表示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
}
複製代碼

固然,性能會差不少,由於太多遍歷了。通用版本也不難,能夠本身封裝一個。

3. 總結

其實第一種方法叫作拒絕採樣。咱們知道等機率生成某個範圍的隨機數,想經過這個函數生成一個更小範圍的隨機數,就應該這樣子:超過預期範圍,從新抽取,因此叫作拒絕採樣。 基本的操做:

//咱們仍是用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也是能夠作到。剛恰好就是最完美的,若是目標生成器是質數,就讓拒絕採樣次數儘可能少,也就是儘可能靠近目標。這種隨機數擴展, 套路就是超過的拒絕採樣,不足的利用加法和乘法使得剛恰好到目標範圍或者超過目標

相關文章
相關標籤/搜索