通过随机数的区间分布实现一个抽奖算法。接受一个包含奖品中奖概率的list,返回中奖的奖品。
先定义一个抽象奖品类。
public abstract class AbstractPrize {
/**
* 奖品概率
*/
private double probability;
public double getProbability() {
return probability;
}
public void setProbability(double probability) {
this.probability = probability;
}
}
抽奖算法,思路是通过奖品中奖概率的最大小数位数获得一个基数,比如奖品为a,b,c,d,e中奖概率分别为0.3,0.050,0.1,0.3,0.25,那么得到基数为1000,奖品分布区间为0<=a<300,300<b<350,350<=c<450,450<=d<750,750<=e<1000,随后获得一个0-1000的随机数,顺序遍历奖品(很重要,不能多线程遍历),<300返回a,<350返回b,<450返回c,<750返回d,<1000返回e.
/**
* 抽奖算法
* @param prizes
* @param <T>
* @return
* @throws IllegalArgumentException
*/
public static <T> T lotteryFunction(List<? extends AbstractPrize> prizes) throws IllegalArgumentException{
prizes = prizes.stream().filter(e -> e.getProbability() != 0d).collect(Collectors.toList());
Double base = this.verification(prizes);
int index = 0;
if(base != 0d) {
BigDecimal fixRate = new BigDecimal("0");
//产生随机数
Random random = new Random();
long result = nextLong(random, base.longValue());
BigDecimal res = new BigDecimal(String.valueOf(result));
//计算奖品分布区间
for (AbstractPrize prize : prizes) {
if(prize.getProbability() == 0d){
continue;
}
fixRate = fixRate.add(new BigDecimal(String.valueOf(prize.getProbability()))
.multiply(new BigDecimal(String.valueOf(base))));
Boolean flag = res.compareTo(fixRate) == -1;
if (flag) {
break;
}
index++;
}
if(index <= prizes.size() - 1 ) {
return (T) prizes.get(index);
}
}
return null;
}
/**
* 产生一个指定范围内的long型随机数
* @param rng
* @param n
* @return
*/
private static long nextLong(Random rng, long n) {
long bits, val;
do {
bits = (rng.nextLong() << 1) >>> 1;
val = bits % n;
} while (bits-val+(n-1) < 0L);
return val;
}
/**
* 计算基数精度,校验所有概率和不能超过1
* @param prizes
* @return
* @throws IllegalArgumentException
*/
private static Double verification(List<? extends AbstractPrize> prizes) throws IllegalArgumentException{
BigDecimal rateSum = new BigDecimal("0");
Double base = 0d;
for(AbstractPrize prize : prizes ) {
rateSum = rateSum.add(new BigDecimal(String.valueOf(prize.getProbability())));
Boolean verify = prize.getProbability() < 0 || rateSum.compareTo(new BigDecimal("1")) == 1;
if(verify){
throw new IllegalArgumentException("probability error");
}
String str = prize.getProbability() + "";
int len = str.length() - str.indexOf(".") - 1;
//最小基数为100,好像没必要
Double temp = Math.pow(10,len < 2 ? 2 : len);
if(temp > base){
base = temp;
}
}
return base;
}