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