在一些抽奖活动中,会有按概率抽取奖品的操作,本文将提供一个抽奖概率的解决方案。
- 假设抽奖集合如下:
奖品id | 概率 |
---|---|
1 | 10% |
2 | 10% |
3 | 20% |
4 | 20% |
- 生成集合
生成的连续集合为
{(0.0, 10.0],(10.0, 30.0],(30.0, 60.0]}
,如下:
奖品id | 区间 |
---|---|
1 | (0.0, 10.0] |
2 | (10.0, 20.0] |
3 | (20.0, 40.0] |
4 | (40.0, 60.0] |
最大的概率值 maxElement = 60.0
,这个值下面会用到。
- 生成随机数
生成方法为 random.nextDouble() * maxElement
,maxElement
就是上一步得到的。
- 判断随机数在哪个区间内,返回该区间对应的奖品id
假如 生成了随机数12.001
,则它属于(10.0, 20.0]
,对应的奖品id为2
.
代码如下:
LotteryUtil
public class LotteryUtil {
private List<Continuous> lotteryList;
private double maxElement;
/**
* 得到一个抽奖对象
* @param map 抽奖概率,key:奖品id,value:概率
* @param <T>
* @return
*/
public static<T> LotteryUtil getInstance(Map<T, Double> map) {
return new LotteryUtil(map);
}
/**
* 构造抽奖集合
* @param map 为奖品的概率
*/
private <T> LotteryUtil(Map<T, Double> map) {
if(map.size() == 0){
throw new IllegalArgumentException("集合不能为空!");
}
double minElement = 0d;
lotteryList = new ArrayList<>(map.size());
for(Map.Entry<T, Double> entry : map.entrySet()) {
if(null == entry.getValue() || entry.getValue() <= 0) {
continue;
}
minElement = maxElement;
maxElement = maxElement + entry.getValue();
Continuous continuous = new Continuous(entry.getKey(), minElement, maxElement);
lotteryList.add(continuous);
}
}
/**
* 抽奖操作
*
* @return
*/
public <T>T lottery() {
Random r = new Random();
//生成随机数
double d = r.nextDouble() * maxElement;
if(d == 0d) {
// 如果生成了0,就再生成一次
d = r.nextDouble() * maxElement;
}
int size = lotteryList.size();
for(int i = 0; i < size; i++){
Continuous continuous = lotteryList.get(i);
if(continuous.isContainKey(d)){
return (T)continuous.getVal();
}
}
return null;
}
/**
* 定义一个连续区间
* 集合中元素x满足:(minElement, maxElement]
* 数学表达式为:minElement < x <= maxElement
*
*/
class Continuous {
private Object val;
private double minElement;
private double maxElement;
public Continuous(Object val, double minElement, double maxElement) {
this.val = val;
this.minElement = minElement;
this.maxElement = maxElement;
}
/**
* 判断当前集合是否包含特定元素
* @param element
* @return
*/
public boolean isContainKey(double element){
boolean flag = false;
if(element > minElement && element <= maxElement){
flag = true;
}
return flag;
}
public Object getVal() {
return val;
}
public void setVal(Object val) {
this.val = val;
}
public double getMinElement() {
return minElement;
}
public void setMinElement(double minElement) {
this.minElement = minElement;
}
public double getMaxElement() {
return maxElement;
}
public void setMaxElement(double maxElement) {
this.maxElement = maxElement;
}
}
}
测试类:
LotteryUtilTest
public class LotteryUtilTest {
@Test
public void testLottery() {
Map<Integer, Double> map = new HashMap<>();
map.put(1, 0.10d);
map.put(2, 0.10d);
map.put(3, 0.20d);
map.put(4, 0.20d);
Map<Integer, Integer> retMap = new HashMap<>();
int times = 100000;
LotteryUtil lotteryUtil = LotteryUtil.getInstance(map);
for(int i = 0; i < times; i++) {
Integer result = lotteryUtil.lottery();
Integer num = retMap.get(result);
retMap.put(result, null == num ? 1 : num + 1);
}
System.out.println(retMap);
Map<Integer, Double> setRate = new HashMap<>();
double total = 0d;
for(Map.Entry<Integer, Double> entry : map.entrySet()) {
total += entry.getValue();
}
for(Map.Entry<Integer, Double> entry : map.entrySet()) {
setRate.put(entry.getKey(), entry.getValue() / total);
}
Map<Integer, Double> realRate = new HashMap<>();
for(Map.Entry<Integer, Integer> entry : retMap.entrySet()) {
realRate.put(entry.getKey(), (double)(entry.getValue()) / times);
}
for(Map.Entry<Integer, Double> entry : realRate.entrySet()) {
Integer key = entry.getKey();
System.out.println(String.format("奖品id为:%s, 抽奖次数为:%s, 设定的概率为:%s,实际的概率为:%s",
key, times, setRate.get(key), realRate.get(key)));
}
}
}
运行结果:
{1=16611, 2=16565, 3=33412, 4=33412}
奖品id为:1, 抽奖次数为:100000, 设定的概率为:0.16666666666666666,实际的概率为:0.16611
奖品id为:2, 抽奖次数为:100000, 设定的概率为:0.16666666666666666,实际的概率为:0.16565
奖品id为:3, 抽奖次数为:100000, 设定的概率为:0.3333333333333333,实际的概率为:0.33412
奖品id为:4, 抽奖次数为:100000, 设定的概率为:0.3333333333333333,实际的概率为:0.33412
本文原文地址: https://my.oschina.net/funcy/blog/4732830 ,支持原创,转载请说明出处。