美文网首页
一个经典的指定概率的抽奖算法

一个经典的指定概率的抽奖算法

作者: NemoExpress | 来源:发表于2020-11-09 14:18 被阅读0次

公司需要做一个抽奖的页面,奖项的概率要能控制。发现一个经典算法,记录一下。

var p_arr = [10, 20, 30, 40];
function getResult(arr) {
    var pSum = eval(arr.join("+")); // 获取总概率区间
    for (var i = 0; i < arr.length; i++) {
        var random = parseInt(Math.random() * pSum); // 获取 0-总概率区间的一个随随机整数
        if (random < arr[i]) {
            return i; //如果在当前的概率范围内,得到的就是当前概率
        } else {
            pSum -= arr[i]; //否则减去当前的概率范围,进入下一轮循环
        }
    }
}
getResult(p_arr);

首先定义一个数组 [10,20,30,40]对应各个奖项的概率, 该数组各元素的总和代表总概率区间为 100,第一个元素的值为 10,就正好代表抽中第一个的概率为 10%,其他元素的概率依此类推。

  • 第一轮循环开始是从 0-100 这个概率范围内筛选第一个数看她是否在他的出现概率范围之内,如果出现,这直接返回该结果,退出循环。
  • 如果不在,则将概率空间,也就是数组总和(pSum)减去刚刚的那个数字的概率空间,然后进行第二轮循环
  • 在本例当中就是减去 10,也就是说第二个数是在 0-90 这个范围内筛选的。
  • 这样筛选到最终,总会有一个数满足要求。
  • 就相当于去一个箱子里摸东西,第一个不是,第二个不是,第三个还不是,那最后一个一定是。

理解

  1. 假设为 a,b,c,d,这四个概率数,那么他们的概率空间(总和)就是 a+b+c+d

  2. 从 0-(a+b+c+d) 中取一个随机数,把这个随机数跟 a 做对比,情况有两种:

比 a 小,概率为 a/(a+b+c+d)
比 a 大,概率为(b+c+d)/(a+b+c+d)

  1. 如果取到的值比 a 小,那么说明已经抽中 a 了,直接返回。抽奖结束;如果取到的值比 a 大,说明没有抽到 a,则进行下一轮
  2. 在第二轮中,需要在之前的概率空间(总和)中去掉 a 的空间,所以第二轮的概率空间为 b+c+d,然后从新的概率空间 0-(b+c+d)中取一个随机数跟 b 做对比,同样情况有两种:

比 b 小,那么它的概率就是 b/(b+c+d). 注意,这个概率出现的前提是前一次的结果要 > a ,所以总概率就是两次乘积: (b+c+d)/(a+b+c+d) * b/(b+c+d) = b/(a+b+c+d)
比 b 大,那么它的概率就是 (c+d)/(b+c+d). 同样,和 >a 的结果同时出现,这种情况的总概率应该是: (b+c+d)/(a+b+c+d) * (c+d)/(b+c+d) = (c+d)/(a+b+c+d).

  1. 如果取到的值比 b 小,那么说明已经抽中 b 了,直接返回。抽奖结束;如果取到的值比 b 大,说明没有抽到 b,然后进入第三次循环,然后第三次循环时,我们再把 b 的概率空间减去,以此类推……

应用

prize_arr 代表奖品选项,V 就代表每个奖品所对应的概率

var prize_arr = [
    { id: 1, v: 5, prize: "一等奖-80%OFF-平板电脑", coup: "AM-80-OFF-CO" },
    { id: 2, v: 10, prize: "二等奖-60%OFF-数码相机", coup: "AM-60-OFF-CO" },
    { id: 3, v: 15, prize: "三登奖-40%OFF-音箱设备", coup: "AM-40-OFF-CO" },
    { id: 4, v: 20, prize: "四等奖-30%OFF-4G优盘", coup: "AM-30-OFF-CO" },
    { id: 5, v: 50, prize: "五等奖-20%OFF-谢谢参与", coup: "AM-20-OFF-CO" },
];

var gArr = [];
for (var i = 0; i < prize_arr.length; i++) {
    gArr.push(prize_arr[i]["v"]);
}
console.log(prize_arr[getResult(gArr)]["prize"]);

问题

结合我公司抽奖实际,发现奖项信息全部在 js 里面写死,这就存在一隐患,只需要稍微懂一点前端知识的人,一看到代码就能获取到所有奖品里面的信息,而恰好我们的奖品都是电子折扣码,就是上面奖品列表中的 coup 字段,那么这个抽奖就变得毫无意义。
【解决办法】
方案一:可以将奖品数据字段进行简单的加密,然后再解密,这样处理使得奖品信息不是太过于直白而已,治标不治本。
方案二:将抽奖代码放在后台,通过前端请求的方式完成,每次请求都只是返回对应抽到的奖品信息,完美解决。 附上 php 实现的后端代码

<?php
/*
 * 经典的概率算法,
 * $proArr是一个预先设置的数组,
 */
function get_rand($proArr) {
    $result = '';
    //概率数组的总概率精度
    $proSum = array_sum($proArr);
    //概率数组循环
    foreach ($proArr as $key => $proCur) {
        $randNum = mt_rand(1, $proSum);
        if ($randNum <= $proCur) {
            $result = $key;
            break;
        } else {
            $proSum -= $proCur;
        }
    }
    unset ($proArr);
    return $result;
}

/*
 * 奖项数组
 * 是一个二维数组,记录了所有本次抽奖的奖项信息,
 * 其中id表示中奖等级,prize表示奖品,v表示中奖概率。
 * 注意其中的v必须为整数,你可以将对应的 奖项的v设置成0,即意味着该奖项抽中的几率是0,
 * 数组中v的总和(基数),基数越大越能体现概率的准确性。
 * 本例中v的总和为100,那么平板电脑对应的 中奖概率就是1%,
 */
$prize_arr = array(
    '0' => array('id'=>1,'prize'=>'一等奖-80%OFF-平板电脑','v'=>1,'coup'=>'AM-80-OFF-CO'),
    '1' => array('id'=>2,'prize'=>'二等奖-60%OFF-数码相机','v'=>5,'coup'=>'AM-60-OFF-CO'),
    '2' => array('id'=>3,'prize'=>'三登奖-40%OFF-音箱设备','v'=>10,'coup'=>'AM-40-OFF-CO'),
    '3' => array('id'=>4,'prize'=>'四等奖-30%OFF-4G优盘','v'=>12,'coup'=>'AM-30-OFF-CO'),
    '4' => array('id'=>5,'prize'=>'五等奖-20%OFF-10Q币','v'=>22,'coup'=>'AM-20-OFF-CO'),
    '5' => array('id'=>6,'prize'=>'六等奖-0%OFF-下次没准就能中哦','v'=>50,'coup'=>'AM-00-OFF-CO'),
);

/*
 * 将中奖奖品保存在数组$res['yes']中,
 * 而剩下的未中奖的信息保存在$res['no']中,
 * 最后输出json数据给前端页面。
 */
foreach ($prize_arr as $key => $val) {
    $arr[$val['id']] = $val['v'];
}
$rid = get_rand($arr); //根据概率获取奖项id

$res['yes']['prize']= $prize_arr[$rid-1]['prize'];
$res['yes']['coup']= $prize_arr[$rid-1]['coup'];
$res['yes']['prize']= $prize_arr[$rid-1]['prize'];
$res['yes']['id']= $prize_arr[$rid-1]['id'];
// unset($prize_arr[$rid-1]); //将中奖项从数组中剔除,剩下未中奖项
shuffle($prize_arr); //打乱数组顺序
for($i=0;$i<count($prize_arr);$i++){
    $pr[] = $prize_arr[$i]['prize'];
}
$res['no'] = $pr;
$res = json_encode( $res );
print_r($res) ;
?>

抽奖页面实现

抽奖采用转盘的设计,由于考虑到兼容性问题,故采用现成的轮子 jquery.rotate.js 插件开发,在前端请求到对应数据后,轮盘转到指定的位置。

<script src="assets/js/jquery.rotate.js"></script>;
....
$(function () {
    var rotateFunc = function (awards, angle, text) {
        isture = true;
        $btn.stopRotate();
        $btn.rotate({
            angle: 0, //旋转的角度数
            duration: 4000, //旋转时间
            animateTo: angle + 1440, //给定的角度,让它根据得出来的结果加上1440度旋转
            callback: function () {
                isture = false; // 标志为 执行完毕
                alert(text);
            },
        });
    };
    var clickfunc = function () {
       $.get('chou.php', function (data, status) {
            if (status == 'success') {
                var prize = JSON.parse(data).yes
                // var data = [1, 2, 3, 4, 5, 6];//抽奖
                //data为随机出来的结果,根据概率后的结果
                // dataId = data[Math.floor(Math.random() * data.length)];//1~6的随机数
                rotateFunc(prize.id, (prize.id - 1) * -60, prize.prize);
            } else {
                console.log("请求失败,请刷新");
            }

        })
    };
    var $btn = $(".g-lottery-img"); // 旋转的div
    var playnum = 5; //初始次数,由后台传入
    $(".playnum").html(playnum); //显示还剩下多少次抽奖机会
    var isture = 0; //是否正在抽奖

    $(".playbtn").click(function () {
        if (isture) return; // 如果在执行就退出
        isture = true; // 标志为 在执行
        if (playnum <= 0) {
            //当抽奖次数为0的时候执行
            alert("没有次数了");
            $(".playnum").html(0); //次数显示为0
            isture = false;
        } else {
            //还有次数就执行
            playnum = playnum - 1; //执行转盘了则次数减1
            if (playnum <= 0) {
                playnum = 0;
            }
            $(".playnum").html(playnum);
            clickfunc();
        }
    });
});

相关文章

网友评论

      本文标题:一个经典的指定概率的抽奖算法

      本文链接:https://www.haomeiwen.com/subject/bxvlbktx.html