今天同事问我有没有抽奖算法代码,虽然之前项目有但是我觉得实现上不是很好也不是很通用,但早在很久之前有思考过这个问题。其实除了算法外抽奖的流程(业务)处理也很重要,为什么?待会说,先来说说算法。
1、抽奖算法
抽奖算法一般会有三个属性:奖品种类、概率、奖品库存。针对不同的抽奖活动来讲奖品种类的数量是不固定的,有可能只有三种奖品,也有可能有10几种奖品。所以我们的算法必须是动态的奖品种类。而我们这种算法可以很天然的解决这个问题。
1.1、算法思路
将所有概率转换为整数类型,随机一个数,这个数在1到奖品种类概率整数总和之间。随机数在对应的奖品概率区间即为中奖,是不是很简单
1.1.1、举例
假如有ABC三种奖品:
| 奖品名称 | 奖品概率 | 奖品百分比 | 奖品数量 |
|---|---|---|---|
| A | 0.3 | 30% | 100 |
| B | 0.3 | 30% | 100 |
| C | 0.3 | 30% | 100 |
因奖品概率都是小数的原因,我们将奖品概率转换成整数,奖所有奖品概率乘以10。因此我们得到的随机数范围是1 ~(0.3 * 10 + 0.3 * 10 +0.3 * 10)= 9,也就是说我们现在随机数的范围应该在1到9之间。
中奖情况如下:
| 奖品名称 | 随机数中奖范围 | 随机数中奖数字 |
|---|---|---|
| A | 1~3 | 1、2、3 |
| B | 4~6 | 4、5、6 |
| C | 7~9 | 7、8、9 |
发展到这里有没有发现其实这个算法有问题?
ABC三个概率加起来不等于1,如果现在的随机数在1-9之间那就是百分百中奖,真实的中奖概率是33.33%,并不是设置的30%。所以为来解决这个问题我们的随机数需要再加上一个谢谢参与的概率,根据ABC的概率我们得出谢谢参与的概率为0.1【1-(0.3+0.3+0.3)=0.1】,因此我们实际的随机数范围应该在1~10之间。
(补充:其实我们不需要累加概率,而是直接找到最小的概率,它转换成整数的乘数就是我们要的最大值范围。)
中奖情况如下:
| 奖品名称 | 随机数中奖范围 | 随机数中奖数字 |
|---|---|---|
| A | 1~3 | 1、2、3 |
| B | 4~6 | 4、5、6 |
| C | 7~9 | 7、8、9 |
| 谢谢参与 | 10 | 10 |
这个例子过于简单,我们现在增加点难度。添加一个奖品D,它的中奖概率是万分之一。
| 奖品名称 | 奖品概率 | 奖品百分比 | 奖品数量 |
|---|---|---|---|
| A | 0.3 | 30% | 100 |
| B | 0.3 | 30% | 100 |
| C | 0.03 | 3% | 100 |
| D | 0.00001 | 0.001% | 3 |
因为我们的算法是用整数计算的,所以我们要找到最小 的奖品概率D,将它换算成整数因此我们得到的随机数范围是1~10000。又因ABCD奖品概率总和小于1。所以根据算法得出如下表格:
| 奖品名称 | 随机数中奖范围 | 随机数中奖数字 |
|---|---|---|
| A | 1~3000 | 1...3000 |
| B | 3001~6000 | 3001...6000 |
| C | 6001~6300 | 6001...6300 |
| D | 6301 | 6301 |
| 谢谢参与 | 6302~10000 | 6302...10000 |
因为我们所有奖品都有库存数量,如果我们中奖的奖品没有库存来怎么办?有些人可能会认为要去掉没库存的奖品重新随机一次,其实这是不可以的,因为这相当于他抽奖来两次。因此如奖品没有库存应该直接返回谢谢参与。
2、抽奖业务流程
为什么说抽奖业务流程的设计也很重要,因为不同的抽奖场景模式而言,为来增加用户体验感需要做一些调整。比如拿刮刮乐来说:一般前端的处理流程如下:
- 进入页面渲染刮刮卡未刮开状态
- 用户开始刮卡
- 调用抽奖接口并返回中奖内容
- 渲染中奖内容
这里会出现一个问题。因为是在第二步用户开始刮卡的时候才去调用抽奖接口,那么就会出现体验不好的问题,接口的响应需要时间,页面渲染奖品图片也需要时间。给到用户的感觉就是:刮开的一瞬间看到的是空白,然后突然又出现奖品。当然如果觉得这个不影响大局也可以这么做。为了解决这个问题需要在用户刮卡之前调用抽奖接口先拿到结果,但是这样懂技术的人可以不刮卡的情况下提前知道中奖结果,这样BUG就出现了,会让很多懂技术的人钻空子。废话不多说了,直接说解决方案把:
- 用户进入页面
- 调用服务端的抽奖接口
- 服务端随机奖品
- 服务端奖奖品存入Redis并设置为未开奖
- 服务端返回奖品给前端页面
- 前端渲染刮刮卡底部奖品
- 用户刮奖显示奖品并调用服务的开奖接口
也就是说当用户进入页面就已经确定用户中了什么奖,只是没告诉用户。需要等到用户刮开以后奖品才发放给用户。如果他通过抓取数据发现自己没中奖退出页面重新进入,服务端会先从Redis缓存中先获取用户未开奖的奖品返回,从而避免懂技术的用户钻漏洞的问题。整体上来讲就是将之前的一个抽奖接口分成了两个:抽奖接口、开奖接口。
当然如果是其他抽奖场景不需要提前渲染的情况可以只用一个接口就行,根据具体的业务需求做调整。