Java 技术探秘

人如果没有了梦想,跟闲鱼有什么区别?


  • 首页

  • 源码分析

  • 日常记录

  • 学习笔记

  • 解决方案

  • 关于

  • Search
close

java抽奖工具类:按概率抽奖

Published at: 2021-04-22   |   Categories: 解决方案     |   Reading: 484 words ~3min

在一些抽奖活动中,会有按概率抽取奖品的操作,本文将提供一个抽奖概率的解决方案。

  1. 假设抽奖集合如下:
奖品id 概率
1 10%
2 10%
3 20%
4 20%
  1. 生成集合 生成的连续集合为{(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,这个值下面会用到。

  1. 生成随机数

生成方法为 random.nextDouble() * maxElement,maxElement 就是上一步得到的。

  1. 判断随机数在哪个区间内,返回该区间对应的奖品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 ,支持原创,转载请说明出处。

#java# #抽奖#
springboot 多数据源下,开启事务后,数据源切换失败的处理
【docker实践】之二:安装redis
  • Table of Content
  • Site Information

Funcy

9 Blogs
4 Categories
24 Tags
Gitee 掘金
© 2009 - 2024 Java 技术探秘
Powered by - Hugo v0.127.0
粤ICP备2024268097号-1
0%