两种抽奖算法实现 2023-04-25 16:53 ## 1、固定奖品抽奖 奖品id为1的,中奖概率为10% 奖品id为2的,中奖概率为20% 奖品id为3的,中奖概率为70% ### 思路 随机1~1000之间的数字: 如果随机的数字在区间[1, 100]内,则得到奖品1;如果随机的数字在区间[101, 300]内,则得到奖品2;如果随机的数字在区间[301, 1000]内,则得到奖品3 ![](https://minio.riun.xyz/riun1/2023-04-24_3kS7VzSwFZNASrJDih.jpg) 实际业务中可能配置中奖概率为0.001%这种,因此随机数字区间需要根据情况动态放大。 ### 算法实现 ```java package com.ax.member.member.controller; import cn.hutool.core.collection.CollectionUtil; import java.math.BigDecimal; import java.util.*; /** * 抽奖核心算法 * @author HanXu */ public class Arithmetic { // 默认放大倍数 private static final int MULRIPLE = 1000000; /** * 抽奖 * @param prizes * @return */ public static Long drawRaffle(List<Prize> prizes) { if (CollectionUtil.isEmpty(prizes)) { throw new IllegalArgumentException("prizes不能为空"); } //放大倍数 int mulriple = getMulriple(prizes); // 划分区间 Map<Long, Integer[]> prizeScopes = new HashMap(16); int lastScope = 0; for (Prize prize : prizes) { Long prizeId = prize.getPrizeId(); int currentScope = lastScope + prize.getProbability().multiply(new BigDecimal(mulriple)).intValue(); prizeScopes.put(prizeId, new Integer[]{lastScope + 1, currentScope}); lastScope = currentScope; } // 获取1-1000000之间的一个随机数 int luckyNumber = new Random().nextInt(mulriple); Long luckyPrizeId = 0L; // 查找随机数所在的区间 Set<Map.Entry<Long, Integer[]>> set = prizeScopes.entrySet(); for (Map.Entry<Long, Integer[]> entry : set) { Long key = entry.getKey(); Integer[] value = entry.getValue(); if (luckyNumber >= value[0] && luckyNumber <= value[1]) { luckyPrizeId = key; break; } } return luckyPrizeId; } public static int getMulriple(List<Prize> prizes) { Collections.sort(prizes); BigDecimal probabilityMin = prizes.get(0).getProbability(); int scale = probabilityMin.scale(); int mulNum = (int) Math.pow(10, scale); return mulNum > MULRIPLE ? mulNum : MULRIPLE; } public static void main(String[] args) { List<Prize> prizes = new ArrayList(); Prize prize1 = new Prize(1L, new BigDecimal("0.01")); prizes.add(prize1); Prize prize2 = new Prize(2L, new BigDecimal("0.19")); prizes.add(prize2); Prize prize3 = new Prize(3L, new BigDecimal("0.2")); prizes.add(prize3); Prize prize4 = new Prize(4L, new BigDecimal("0.0001")); prizes.add(prize4); Prize prize5 = new Prize(5L, new BigDecimal("0.5999")); prizes.add(prize5); int times = 1000; int prize1GetTimes = 0; int prize2GetTimes = 0; int prize3GetTimes = 0; int prize4GetTimes = 0; int prize5GetTimes = 0; for (int i = 0; i < times; i++) { int pay = drawRaffle(prizes).intValue(); System.out.println("抽奖到了" + pay); switch (pay) { case 1: prize1GetTimes++; break; case 2: prize2GetTimes++; break; case 3: prize3GetTimes++; break; case 4: prize4GetTimes++; break; case 5: prize5GetTimes++; break; } } System.out.println("抽奖次数" + times); System.out.println("prize1中奖次数" + prize1GetTimes); System.out.println("prize2中奖次数" + prize2GetTimes); System.out.println("prize3中奖次数" + prize3GetTimes); System.out.println("prize4中奖次数" + prize4GetTimes); System.out.println("prize5中奖次数" + prize5GetTimes); } } package com.ax.member.member.controller; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.math.BigDecimal; /** * 奖品 * @author HanXu */ @Data @AllArgsConstructor @NoArgsConstructor public class Prize implements Comparable<Prize>{ /** * 奖品id */ private Long prizeId; /** * 中奖概率 */ private BigDecimal probability; @Override public int compareTo(Prize o) { return this.probability.compareTo(o.probability); } } ``` ## 2、随机红包 随机抽奖得到现金红包,0.1~1元的,90%概率得到;1~10元的,9%概率得到;10~100元的,1%概率得到; ### 思路 第一阶段思路跟上述一样,但是随机到一个区间后,需要再次在这个区域随机,才能得到最终抽奖金额。 ![](https://minio.riun.xyz/riun1/2023-04-25_3lfD6OcWTOmHgFBrp9.png) 如,第一次随机到数字556,在[1, 900]之间,则说明抽到的现金红包只能是0.1~1之间的数值;然后再次在0.1~1之间随机,得到最终现金红包。 若第一次随机到数字996,在[991, 1000]之间,则说明抽到的现金红包只能是10~100之间的数值;然后再次在10~100之间随机,得到最终现金红包。 ### 算法实现 ```java package com.ax.member.common.response; import org.apache.commons.lang3.RandomUtils; import java.util.ArrayList; import java.util.List; /** * 按几率产生随机数 * 例如,产生0.1-100的随机数,0.1-1的几率是90%,1-10的几率是9%,10-100的几率是1% * @author Administrator */ public class RateRandomNumber { /** * 产生随机数 * @param min 最小值 * @param max 最大值 * @return 随机结果 */ public static double produceRandomNumber(double min, double max) { return RandomUtils.nextDouble(min, max); } /** * 按比率产生随机数 * @param min 最小值 * @param max 最大值 * @param separates 分割值(中间插入数) * @param percents 每段数值的占比(几率) * @return 按比率随机结果 */ public static double produceRateRandomNumber(double min, double max, List<Double> separates, List<Integer> percents) { //校验入参 if (min > max) { throw new IllegalArgumentException("min值必须小于max值"); } if (separates == null || percents == null || separates.size() == 0) { return produceRandomNumber(min, max); } if (separates.size() + 1 != percents.size()) { throw new IllegalArgumentException("分割数字的个数加1必须等于百分比个数"); } int totalPercent = 0; for (Integer p : percents) { if (p < 0 || p > 100) { throw new IllegalArgumentException("百分比必须在[0,100]之间"); } totalPercent += p; } if (totalPercent != 100) { throw new IllegalArgumentException("百分比之和必须为100"); } for (double s : separates) { if (s <= min || s >= max) { throw new IllegalArgumentException("分割数值必须在(min,max)之间"); } } int rangeCount = percents.size(); //构造分割的n段范围 List<Range> ranges = new ArrayList<Range>(); int scopeMax = 0; for (int i = 0; i < rangeCount; i++) { Range range = new Range(); range.min = (i == 0 ? min : separates.get(i - 1)); range.max = (i == rangeCount - 1 ? max : separates.get(i)); range.percent = percents.get(i); //片段占比,转换为[1,100]区间的数字 range.percentScopeMin = scopeMax + 1; range.percentScopeMax = range.percentScopeMin + (range.percent - 1); scopeMax = range.percentScopeMax; ranges.add(range); } //结果赋初值 double r = min; int randomInt = RandomUtils.nextInt(1, 101); for (int i = 0; i < ranges.size(); i++) { Range range = ranges.get(i); //判断使用哪个range产生最终的随机数 if (range.percentScopeMin <= randomInt && randomInt <= range.percentScopeMax) { r = produceRandomNumber(range.min, range.max); break; } } return r; } public static class Range { public double min; public double max; public int percent; //百分比 public int percentScopeMin; //百分比转换为[1,100]的数字的最小值 public int percentScopeMax; //百分比转换为[1,100]的数字的最大值 } public static void main(String[] args) { List<Double> separates = new ArrayList<Double>(); separates.add(1.0); separates.add(10.0); List<Integer> percents = new ArrayList<Integer>(); percents.add(90); percents.add(9); percents.add(1); for (int i = 0; i < 100; i++) { double number = produceRateRandomNumber(0.1, 100, separates, percents); System.out.println(String.format("%.2f", number)); } } } ``` 此代码最重要的是构造各阶段range对象: ![](https://minio.riun.xyz/riun1/2023-04-25_3lfMhDq2tQDFiBjB1M.jpg) 上述代码未引入放大倍数,可以参考第一种抽奖算法完善一下。 ## 参考 随机红包:https://www.codenong.com/cs106780442/ 固定奖品抽奖:https://www.jb51.net/article/256173.htm --END--
发表评论