一个简单抽奖算法的实现以及如何预防超中
2016-06-24 21:45
766 查看
一个简单抽奖算法的实现以及如何预防超中
需求
每个用户每天有3次抽奖机会;
抽奖奖池一共分为6档内容:现金红包1元,2元,3元,5元,iphone6s,谢谢参与;
支持每天调整和配置抽奖的获奖概率;
算法介绍
每种奖品都有一个权重 对应一个区间 若落入该区间就表示中奖 调整区间大小就可改变获奖概率 即调整权重值即可
抽奖的时候 先生成一个随机值
判断该随机值在哪一个区间 如
如果想增大中iphone6s的概率 调整权重值即可 如将权重改为1000, 则区间变为
同时会为每种奖品设置库存 如
中奖后 会减库存 但假如库存只剩1个了 有10个用户同时落入一元区间 如何避免
解决方法
即是否中奖除了落入区间外 还需判断减库存是否成功
如果减库存失败 仍当做未中奖
一旦一种奖品库存为0 下次计算区间的时候 将它排除 如一元奖品库存已为0 这时各奖品的区间变化为
验证上述算法
看是否能抽完所有奖品 如某天的奖品配置如下 (权重默认等于库存)
假设日活用户数为3万 每个用户可抽3次
java代码
输出
可知 假如该天抽奖次数能有9万次的话 可以抽完所有的奖品 另外因是单线程未考虑减库存
失败的情况 即并发减库存的情况
抽奖算法2 存在奖品库存的前提下 保证每次中奖的概率恒定 如15% 抽100次有15次中奖
输出
可见 实际不用到理论抽奖次数 即可抽完所有奖品
需求
每个用户每天有3次抽奖机会;
抽奖奖池一共分为6档内容:现金红包1元,2元,3元,5元,iphone6s,谢谢参与;
支持每天调整和配置抽奖的获奖概率;
算法介绍
每种奖品都有一个权重 对应一个区间 若落入该区间就表示中奖 调整区间大小就可改变获奖概率 即调整权重值即可
奖品 | 权重 | 区间 | |
---|---|---|---|
1元 | 5000 | [0,5000) | |
2元 | 1000 | [5000,6000) | |
3元 | 500 | [6000,6500) | |
5元 | 100 | [6500, 6600) | |
iphone6s | 1 | [6600, 6601) | |
未中奖 | 59409 | [6601,66010) | 假设设定抽10次中一次, 未中奖权重 = 抽检概率导数奖品数-奖品数 = 106601-6601 = 59409 |
randNum = new Random().nextInt(totalWeight); // totalWeight = 上面权重列之和
判断该随机值在哪一个区间 如
randNum = 8944 落在未中奖区间 未中奖 randNum = 944 落在1元区间 中了一元
如果想增大中iphone6s的概率 调整权重值即可 如将权重改为1000, 则区间变为
[6600,7600)
同时会为每种奖品设置库存 如
日期 | 奖品 | 库存 |
---|---|---|
3.1 | 一元 | 5000 |
1-10=-9的情况呢?
解决方法
update award_stock set stock = stock - 1 where award_id = ? and stock > 0;
即是否中奖除了落入区间外 还需判断减库存是否成功
如果减库存失败 仍当做未中奖
一旦一种奖品库存为0 下次计算区间的时候 将它排除 如一元奖品库存已为0 这时各奖品的区间变化为
奖品 | 权重 | 区间 | |
---|---|---|---|
2元 | 1000 | [0,1000) | |
3元 | 500 | [1000,1500) | |
5元 | 100 | [1500, 1600) | |
iphone6s | 1 | [1600, 1601) | |
未中奖 | 59409 | [1601,61010) | 61010/1601=38 此时中奖概率变小了 相当于抽38次中一次 |
看是否能抽完所有奖品 如某天的奖品配置如下 (权重默认等于库存)
日期 | 奖品 | 权重 | 库存 |
---|---|---|---|
3.1 | 1元 | 5000 | 5000 |
3.1 | 2元 | 1000 | 1000 |
3.1 | 3元 | 500 | 500 |
3.1 | 5元 | 100 | 100 |
3.1 | iphone6s | 1 | 1 |
3.1 | 未中奖 | 59409 | 59409 |
java代码
final Map<String, Integer> awardStockMap = new ConcurrentHashMap<>(); // 奖品 <--> 奖品库存 awardStockMap.put("1", 5000); awardStockMap.put("2", 1000); awardStockMap.put("3", 500); awardStockMap.put("5", 100); awardStockMap.put("iphone", 1); awardStockMap.put("未中奖", 59409); //6601*10 -6601 //权重默认等于库存 final Map<String, Integer> awardWeightMap = new ConcurrentHashMap<>(awardStockMap); // 奖品 <--> 奖品权重 int userNum = 30000; // 日活用户数 int drawNum = userNum * 3; // 每天抽奖次数 = 日活数*抽奖次数 Map<String, Integer> dailyWinCountMap = new ConcurrentHashMap<>(); // 每天实际中奖计数 for(int j=0; j<drawNum; j++){ // 模拟每次抽奖 //排除掉库存为0的奖品 Map<String, Integer> awardWeightHaveStockMap = awardWeightMap.entrySet().stream().filter(e->awardStockMap.get(e.getKey())>0).collect(Collectors.toMap(e->e.getKey(), e->e.getValue())); int totalWeight = (int) awardWeightHaveStockMap.values().stream().collect(Collectors.summarizingInt(i->i)).getSum(); int randNum = new Random().nextInt(totalWeight); //生成一个随机数 int prev = 0; String choosedAward = null; // 按照权重计算中奖区间 for(Entry<String,Integer> e : awardWeightHaveStockMap.entrySet() ){ if(randNum>=prev && randNum<prev+e.getValue()){ choosedAward = e.getKey(); //落入该奖品区间 break; } prev = prev+e.getValue(); } dailyWinCountMap.compute(choosedAward, (k,v)->v==null?1:v+1); //中奖计数 if(!"未中奖".equals(choosedAward)){ //未中奖不用减库存 awardStockMap.compute(choosedAward, (k,v)->v-1); //奖品库存一 if(awardStockMap.get(choosedAward)==0){ System.out.printf("奖品:%s 库存为空%n",choosedAward); //记录库存为空的顺序 } } } System.out.println("各奖品中奖计数: "+dailyWinCountMap); //每日各奖品中奖计数
输出
奖品:iphone 库存为空 奖品:5 库存为空 奖品:1 库存为空 奖品:2 库存为空 奖品:3 库存为空 每日各奖品中奖计数: {1=5000, 2=1000, 3=500, 5=100, iphone=1, 未中奖=83399}
可知 假如该天抽奖次数能有9万次的话 可以抽完所有的奖品 另外因是单线程未考虑减库存
失败的情况 即并发减库存的情况
抽奖算法2 存在奖品库存的前提下 保证每次中奖的概率恒定 如15% 抽100次有15次中奖
final Map<String, Integer> awardStockMap = new ConcurrentHashMap<>(); awardStockMap.put("1", 3000); awardStockMap.put("2", 2000); awardStockMap.put("3", 1500); awardStockMap.put("5", 1000); awardStockMap.put("10", 100); awardStockMap.put("20", 10); awardStockMap.put("50", 5); awardStockMap.put("100", 2); // 权重默认等于库存 final Map<String, Integer> awardWeightMap = new ConcurrentHashMap<>(awardStockMap); final Map<String, Integer> initAwardStockMap = new ConcurrentHashMap<>(awardStockMap); int drawNum = 50780; // 理论可以抽完所有奖品所需抽奖次数 = 奖品数×中奖概率导数 = 7617*100/15 final int threshold = 15; //中奖概率 15% Map<String, Integer> dailyWinCountMap = new ConcurrentHashMap<>(); // 每天实际中奖计数 for (int j = 0; j < drawNum; j++) { // 模拟每次抽奖 //确定是否中奖 int randNum = new Random().nextInt(100); if(randNum>threshold){ dailyWinCountMap.compute("未中奖", (k,v)->v==null?1:v+1); continue; //未中奖 } //中奖 确定是哪个奖品 //排除掉库存为0的奖品 Map<String, Integer> awardWeightHaveStockMap = awardWeightMap.entrySet().stream().filter(e->awardStockMap.get(e.getKey())>0).collect(Collectors.toMap(e->e.getKey(), e->e.getValue())); if(awardWeightHaveStockMap.isEmpty()){ //奖池已为空 System.out.printf("第%d次抽奖 奖品已被抽完%n",j); break; } int totalWeight = (int) awardWeightHaveStockMap.values().stream().collect(Collectors.summarizingInt(i->i)).getSum(); randNum = new Random().nextInt(totalWeight); int prev=0; String choosedAward = null; for(Entry<String,Integer> e : awardWeightHaveStockMap.entrySet() ){ if(randNum>=prev && randNum<prev+e.getValue()){ choosedAward = e.getKey(); //落入此区间 中奖 dailyWinCountMap.compute(choosedAward, (k,v)->v==null?1:v+1); break; } prev = prev+e.getValue(); } //减小库存 awardStockMap.compute(choosedAward, (k,v)->v-1); } System.out.println("每日各奖品中奖计数: "); // 每日各奖品中奖计数 dailyWinCountMap.entrySet().stream().sorted((e1,e2)->e2.getValue()-e1.getValue()).forEach(System.out::println); awardStockMap.forEach((k,v)->{if(v>0){ System.out.printf("奖品:%s, 总库存: %d, 剩余库存: %d%n",k,initAwardStockMap.get(k),v); }});
输出
第47495次抽奖 奖品已被抽完 每日各奖品中奖计数: 未中奖=39878 1=3000 2=2000 3=1500 5=1000 10=100 20=10 50=5 100=2
可见 实际不用到理论抽奖次数 即可抽完所有奖品
相关文章推荐
- UDP协议实现对等通讯Java+RSA加密解密传送信息实现
- Codeforces Round #359 (Div. 2) 部分题解
- 抽奖/红包算法
- Ext.Net学习网站
- css3翻转后显示背部隐藏的元素的效果3D翻转效果- transform rotate backface-visibility
- Ext.Net学习网站
- APP架子迁移指南(三)
- 指定表单使用的路由 Specifying the Route Used by a Form
- SVN之三:Visualsvn Server简易部署及目录权限
- C/C++ 多线程编程方法
- BZOJ 2661 连连看
- Android绘图基础之: Canvas 和 Paint
- gdb-为程序设置运行的参数
- dockerfile-python
- (通俗版)手机漫游的实现原理
- linux系统下修改文件夹目录权限
- Java中的垃圾回收
- AsyncTask
- ReactJS学习系列课程(组件的生命周期)
- MUI动态加载数据后,scrollToBottom无效的解决方案