使用 rand7 实现 rand10 的解决办法及扩展

使用 rand7 实现 rand10 的解决办法及扩展

一个误区引出问题的本质

初看这个问题应该很简单啊,7 个盒子分为两分部:5 + 2。第二部分 2 ,有两种状态:乘以/不乘以 到第一部分上去,不就得到 10 个状态了?刚好可以表示 10 空间。那也就得到 rand10 了。在这里插入图片描述
细想一下不对:如果"第一部分数字"是 1,乘2得到2。如果"第一部分数字"是2,乘以1得到2。这丙种情况都得到 2:比得到 1 的情况要多。则不是等概率的了。

问题的关键点1:在实现 randn 的过程中,需要多次调用 randm,需要保证:后续调用的 randm 得到的值,与之前得到的值,进行运算之后得到的值,不能有重复。否则等概率就无法得到保证了。

问题的关键点2:用 randm 来实现 randn,考虑当 m>n 的情况。稍加思考,这是很简单的,只需要判断 randm() 值 <n 时,返回即可,因为 [0, m-1] 是等概率的,则 [0, n-1] 上每个数字也是等概率的。

如何实现及扩展 (randm -> randn)

由 “后续调用得到的值,与之前得到的值,进行运算,不能有重复”。这一模型,很容易想到二进制数模型:第 n+1 次得到数字 1,计算得到 2^(n+1) ,与之前 n 次得到的数字必然都不一样。
故得到最终的方案:将每次 randm() 的值串起来为一个 m 进制的数字,则它的大小必然会在第 p 次超过 n,记下 p:则调用 p 次 randm 一定可实现 randn ,且此时次数是最少的。

具体代码如下:


function rand(m){
    return Math.floor(Math.random() * m);
}

//利用 rand_m 实现 rand_n
//先求最小的 p 满足: m^p >= n, 比如 m=7, n=10 时, p=2, 表示需要调用 rand7 两次用来生成 rand10
function byRand(m, n){
    let p = Math.floor(Math.log(n) / Math.log(m))

    if(0 === p || Math.pow(m, p) < n){
        ++ p;
    }
    
    //生成的随机数小于 n 则返回, 否则循环生成.
    while(true){
        let res = 0;
        for(let i=0;i<p;++i){
            res = res*m + rand(m);
        }
        if(res < n)
            return res;
    }
    
    return NaN;
}

function Test(){
    const cases = 10000000;
    let statMap = new Map();
    for(let i=0;i<cases;++i){
        let r = byRand(7, 10);      //rand(10)
        
        let count = statMap.get(r)
        if(!count){
            count = 1;
        }
        else{
            ++ count;
        }
        statMap.set(r, count);
    }
    
    let entryList = [];
    for(let [k,v] of statMap){
        entryList.push({num: k, count: v});
    }
    entryList.sort(function(left, right){
        if(left.num < right.num) return -1;
        else if(left.num > right.num) return 1;
        else return 0;
    });
    
    for(let e of entryList){
        console.info(e.num, e.count)
    }
}

Test()

Test函数进行等概率验证:byRand(7, 10) 和原生的 rand(10),分别测试 1000 万次,分别得到的数字分布如下图:
在这里插入图片描述
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值