最近一次面试被问到一个概率抽奖的问题,记录一下,题目是:实现一个抽奖系统,抽到一等奖的概率为10%,二等奖的概率为20%,三等奖的概率为30%,四等奖的概率为40%

写出来之后,又让我写一个测试代码,跑1000次,看结果概率是否接近。

作为前端入门的代码萌新,遇到这种问题,只能临场发挥,从来没写过后台的逻辑~

let _prize = ['A','B','C','D'] //奖项
let _prop = [1, 2, 3, 4] //权重
let a = 0, b = 0, c = 0, d = 0; //统计抽到每个奖的次数 
let count = 0; //统计测试次数
 
//根据奖项和权重生成奖池(如果奖项少,概率简单,可以不用这一步,直接手写奖池)
let generatePool = (prize, prop) => {
    let pool = [];
    for (let i = 0; i < prop.length; i++) {
        for (let j = 0; j < prop[i]; j++){
            pool.push(prize[i])
        }
    }
    return pool
}
 
let newPool = generatePool(_prize, _prop) //生成此次奖池
let poolLen = newPool.length   //奖池容量
 
 
//抽奖
let getPrize = () => {
    let random = Math.floor(Math.random() * poolLen) //奖池容量里的一个随机数
    switch (newPool[random]){ 
        case 'A':
            a++
            break
        case 'B':
            b++
            break
        case 'C':
            c++
            break
        case 'D':
            d++
            break
    }
    count++; //统计测试次数
}
 
 
 
//重复测试n次,并计算概率
let computeP = (func, times) => {
    while (count < times){
        func()
    } 
    let res = [a, b, c, d]                                                                                                                                                                                                                          
    res.forEach((item, k) => {
        res[k] = (item/100/poolLen).toFixed(2)
    })
    return res
}
 
//测试1000次
console.log(computeP(getPrize, 1000))
 
//下面是跑出来的几次结果,还是比较稳定的
//> ["0.10","0.20","0.32","0.38"]
//> ["0.09","0.20","0.29","0.41"]
//> ["0.08","0.20","0.29","0.42"]
//> ["0.10","0.18","0.30","0.42"]

写下来发现,自己写的还是太菜,已经尽力去抽象,但是很多函数还是不能拿来复用,每次需要改一些数据才行,但是结果来讲是好的

10/17/2021 更新

根据其他人的博客(忘了在哪看的了)发现了一个更高效的算法,利用离散化和二分查找来实现

假设需抽到ABCDE的概率为1%,3%,6%,30%,60%

为了避免像之前一样生成一个大数组Pool,我们直接取边界值来讨论会更方便。从1-100的正整数中随机取一个数,取到1就是奖A,取到2、3、4就是奖B,依此类推,我们只需要判断生成的随机数在哪一个区间即可。

这里我们可以按顺序从小到大查找这个随机数,也可以二分查找。这里用二分可以降低时间复杂度到logN,通过判断区间,输出一个奖项,然后重复执行计算概率


let _prize = ['A','B','C','D','E']
let _prop = [1, 4, 10, 40, 100] //累计分布函数
let a = 0, b = 0, c = 0, d = 0, e = 0;
let count = 0;
 
let getPrize = () => {
    let random = Math.ceil(Math.random() * 100) //生成1-100的正整数
    //二分查找
    let low = 0
    let high = _prop.length - 1
    count++;
    while (low < high){
        let mid = Math.floor((low + high) / 2)
        if (random > _prop[mid] && random <= _prop[mid + 1]){
            return _prize[mid + 1]
        } else if (random > _prop[mid + 1]){
            low = mid 
        } else {
            high = mid 
        }
    }
    return _prize[0]
}
 
let computeP = (func, times) => {
    while (count < times){
        let cur_prize = func()
        switch (cur_prize){
            case 'A':
                a++
                break
            case 'B':
                b++
                break
            case 'C':
                c++
                break
            case 'D':
                d++
                break
            case 'E':
                e++
                break
        }
    }   
    let res = [a, b, c, d, e]                                                                                                                                                                                                                         
    res.forEach((item, k) => {
        res[k] = (item/times).toFixed(3)
    })
    return res
}
console.log(computeP(getPrize, 1000))
 
//> ["0.012","0.033","0.056","0.300","0.599"]
//> ["0.015","0.026","0.045","0.308","0.606"]

RyanWu
29 声望2 粉丝

目前只会React,热衷于探索前端未来的方向,node/区块链/图形等