搞了个抽奖活动,中大奖中的简直爽歪歪?
不过还是要偷偷告诉你:抽奖是有内幕的~~~(不能让领导看到,会被打?)
总在各种app上看到转盘抽奖,翻牌抽奖,每次抱着中最大奖的心,换来的都是未中奖的提示,可谓是竹篮打水一场空,屡战屡败,屡败屡战~
现在就聊聊中奖的事,搬好小板凳排队做好了?♂️??♂️??♂️????????????
一、实现思路(这个应该不难理解吧~)
每个奖品都有对应的中奖概率,先对所有奖品中奖概率求和
计算出每个奖品在0-1之间所占的区间块
随机产生0-1之间的随机数,随机数落在哪个区间,就是中奖哪个
例如现有以下奖品:
奖品A 中奖概率为 0.1
奖品B 中奖概率为 0.01
奖品C 中奖概率为 0.001
奖品D 中奖概率为 0.8
第一步:求出概率总和 0.1+0.01+0.001+0.8 = 0.911
第二步:计算每个奖品的所占区间块
奖品A: 0.1 / 0.911 = 0.1098
奖品B: (0.1+0.01)/ 0.911 = 0.1207
奖品C:(0.1+0.11+0.001)/ 0.911 = 0.1218
奖品D:(0.1+0.11+0.001+0.8)/ 0.911 = 1
则:
奖品A的所占区间为:0~0.1098
奖品B的所占区间为:0.1098~0.1207
奖品C的所占区间为:0.1207~0.1218
奖品D的所占区间为:0.1218~1
到此为止思路应该了解了,不过我要做的是这个的升级版
根据不同类型的人分别设置概率(emm~换句话说根据身份设置概率发放奖品)
特此说明一下:里面的概率10代表10%,可以精确到小数点后两位,比如 0.01%
二、干货如下
import lombok.Data;
import lombok.ToString;
/**
* @Author: yansf
* @Description:奖励实体
* @Date:Creat in 10:35 AM 2019/1/16
* @Modified By:
*/
@Data
@ToString
public class RewardDto {
/**
* 奖品id
*/
private int welfareId;
/**
* 天天福利管理Id(Welfare_ManagerId)
*/
private int welfareMgrId;
/**
* 奖品名称
*/
private String welfareName;
/**
* 奖品类型(游戏币10 vip试用20 京东卡30)
*/
private int welfareType;
/**
* 奖品数量(单个奖品上限)
*/
private int AwardCount;
/**
* 奖品值
*/
private int welfareValue;
/**
* 奖品概率 (10代表10%)
*/
private int AwardPct;
/**
* 子概率-A类概率
*/
private int APct;
/**
* 子概率-B类概率
*/
private int BPct;
/**
* 子概率-C类概率
*/
private int CPct;
/**
* 子概率-D类概率
*/
private int DPct;
/**
* 奖品剩余库存
*/
private int remainderAmount;
/**
* 奖品配置库存
*/
private int totalAmount;
}
/**
* @Author: yansf
* @Description:userType 1-A类人 2-B 5-C 7-D
* @Date: 11:28 AM 2019/9/5
* @Modified By:
*/
public RewardDto lottery1(int userId, boolean isVip, int userType) {
RewardDto dto = null;
RewardDto dto1 = null;
//获取奖品配置信息
List<RewardDto> list = welfareMapper.getRewardPctList();
if (dto == null) {
OptionalInt s = list.stream().filter(e -> e.getWelfareType() == 10).mapToInt(RewardDto::getWelfareValue).min();
dto1 = list.stream().filter(e -> e.getWelfareValue() == s.getAsInt()).findAny().orElse(null);
}
//移除库存为0的奖品
list.removeIf(e -> e.getRemainderAmount() == 0);
var firstReward = welfareMapper.getWelfareReceiveRecord(userId, null, null);
//近一个月内是否中奖过vip试用,不再抽中vip
var vipTrial = welfareMapper.getvipTrialRecord(userId);
//如果已经是vip,则不可抽vip
if (isVip || vipTrial > 0) {
list.removeIf(e -> e.getWelfareType() == 20);
}
//如果活动间第一次抽奖,中最小的奖的概率为0
if (firstReward <= 0 && (list.size() >= 2)) {
//移除最小奖的配置
OptionalInt s = list.stream().filter(e -> e.getWelfareType() == 10).mapToInt(RewardDto::getWelfareValue).min();
list.removeIf(e -> e.getWelfareValue() == s.getAsInt() && e.getWelfareType() == 10);
}
if (list != null && list.size() > 0) {
//总的概率区间
float totalPro = 0f;
//存储每个奖品新的概率区间
List<Float> proSection = new ArrayList<Float>();
DecimalFormat df = new DecimalFormat("######0.00");
int random = -1;
try {
//计算总权重
double sumWeight = 0;
for (RewardDto award : list) {
if (userType == 1) {
sumWeight += award.getAPct();
} else if (userType == 2) {
sumWeight += award.getBPct();
} else if (userType == 5) {
sumWeight += award.getCPct();
} else if (userType == 7) {
sumWeight += award.getDPct();
}
}
//产生随机数
double randomNumber;
randomNumber = Math.random();
//根据随机数在所有奖品分布的区域并确定所抽奖品
double d1 = 0;
double d2 = 0;
for (int i = 0; i < list.size(); i++) {
if (userType == 1) {
d2 += Double.parseDouble(String.valueOf(list.get(i).getAPct())) / sumWeight;
if (i == 0) {
d1 = 0;
} else {
d1 += Double.parseDouble(String.valueOf(list.get(i - 1).getAPct())) / sumWeight;
}
} else if (userType == 2) {
d2 += Double.parseDouble(String.valueOf(list.get(i).getBPct())) / sumWeight;
if (i == 0) {
d1 = 0;
} else {
d1 += Double.parseDouble(String.valueOf(list.get(i - 1).getBPct())) / sumWeight;
}
} else if (userType == 5) {
d2 += Double.parseDouble(String.valueOf(list.get(i).getCPct())) / sumWeight;
if (i == 0) {
d1 = 0;
} else {
d1 += Double.parseDouble(String.valueOf(list.get(i - 1).getCPct())) / sumWeight;
}
} else if (userType == 7) {
d2 += Double.parseDouble(String.valueOf(list.get(i).getDPct())) / sumWeight;
if (i == 0) {
d1 = 0;
} else {
d1 += Double.parseDouble(String.valueOf(list.get(i - 1).getDPct())) / sumWeight;
}
}
if (randomNumber >= d1 && randomNumber <= d2) {
random = i;
break;
}
}
} catch (Exception e) {
System.out.println("生成抽奖随机数出错,出错原因:" + e.getMessage());
// throw e;
}
if (random != -1) {
dto = list.get(random);
}
}
if (dto == null) {
dto = dto1;
}
return dto;
}
/**
* @Author: yansf
* @Description:抽奖测试
* @Date: 3:35 PM 2019/8/19
* @Modified By:
*/
@GetMapping(value = "lottery")
public ResponseUtil lottery(int userId, boolean isVip, int userType) {
int coin = 0;
int vip = 0;
int jd = 0;
RewardDto result = new RewardDto();
List<RewardDto> list = new ArrayList();
System.out.println("抽奖开始");
for (int i = 0; i < 1000; i++) {
result = welfareService.lottery(userId, isVip, userType);
if (result.getWelfareType() == 10) {
coin += 1;
} else if (result.getWelfareType() == 20) {
vip += 1;
} else if (result.getWelfareType() == 30) {
jd += 1;
}
list.add(result);
}
Map<String, List<RewardDto>> count = list.stream().collect(Collectors.groupingBy(RewardDto::getWelfareName));
if (result != null) {
return ResponseUtil.response(200, "游戏币:" + coin + ",vip:" + vip + ",京东卡:" + jd, count);
} else {
return ResponseUtil.response(500, "未中奖", count);
}
}
此处循环1000次,使用非vipD类人做一下测试,结果如下图
概率抽奖其实很简单,如果又要分奖品概率,又要根据不同人设置不同的概率,那就比重相乘然后相加,举个简单的例子:
//计算总权重
double sumWeight = 0;
for (RewardDto award : list) {
if (userType == 1) {
//此处奖品概率*不同人抽到该奖品的概率
sumWeight += award.getAPct()*avard.getPct();
}
}
for (int i = 0; i < list.size(); i++) {
if (userType == 1) {
//概率相乘
d2 += Double.parseDouble(String.valueOf(list.get(i).getAPct()*list.get(i).getPct())) / sumWeight;
if (i == 0) {
d1 = 0;
} else {
d1 += Double.parseDouble(String.valueOf(list.get(i - 1).getAPct()*list.get(i).getPct())) / sumWeight;
}
}
看到这,你应该学会了吧???
(不得不diss下,多数抽奖活动大奖概率设置的很低甚至为0,大奖可谓是可望不可及,所以还是好好工作比较靠谱,写bug,找bug,改bug ???emm~)