指定概率的抽奖算法

抽奖算法理论

在一组奖品中,每个奖品有自己的概率,总概率为 1.0,也就是说在库存充足的情况下,必然能抽中其中的一个。

通过「谢谢参与」来作为无奖的奖品(也是一种奖品)。

需要注意的是:如果一组中所有的奖品,总概率之和不为 1.0,那么数值代表的概率就不是真实概率了,需要用所占比例来作为新的概率:新概率值=奖品概率/总概率。

举个例子:只有 A 和 B 两个奖品,A 概率是 0.1,B 概率是 0.3,那么总概率就是 0.4,A 的真实概率就是0.1/0.4=0.25,B 的真实概率是0.3/0.4=0.75,真实的总概率依然为1。

所以如果想要配置的奖品概率正好是抽奖时的概率值,那么就需要为这一组奖品列表的总概率配置成1.0;

代码实现

import lombok.Getter;
import lombok.Setter;

/**
 * 奖品基类
 *
 */
@Setter
@Getter
public class BaseAward {
    /**
     * 抽中这个奖品的概率
     */
    private Double probability;
}


import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

/**
 * 奖品实现类
 */
@Setter
@Getter
@ToString
public class Award extends BaseAward {
    private Integer id;
    private String name;
    private Double price;
    private Integer stock;

    public Award(Integer id, String name, Double price, Double probability, Integer stock) {
        super();
        this.id = id;
        this.name = name;
        this.price = price;
        this.stock = stock;
        setProbability(probability);
    }
}

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.RandomUtil;

import java.util.List;

/**
 * 抽奖工具类
 */
public class LotteryTool {

    /**
     * 抽奖方法
     *
     * @param awardList 奖品列表,这些是备选的奖品(一定有库存的)
     * @param <T>       具体奖品类型
     * @return 返回一个抽中的奖品
     */
    public static <T extends BaseAward> T draw(List<T> awardList) {
        if (CollUtil.isEmpty(awardList)) {
            return null;
        }

        // 获取总概率,当奖品总概率正好为1时,奖品的 probability 就是真实的概率,否则会按新的比例作为概率
        double sumProbability = awardList.stream()
                .map(BaseAward::getProbability)
                .reduce(0.0, Double::sum);

        // 一共会尝试 awardList.size() 次,确保能返回一个奖品
        for (T t : awardList) {

            // 使用随机值,左闭右开(包含0,不包含1)
            if (t.getProbability() > RandomUtil.randomDouble(0.0, 1.0) * sumProbability) {
                return t;
            }
            sumProbability = sumProbability - t.getProbability();
        }

        // 其它情况,会到这里(理论上,一定到不了这里的。)
        return null;
    }
}



import cn.hutool.core.util.StrUtil;

import java.util.ArrayList;
import java.util.List;
/**
* 测试
*/
public class Main {
    public static void main(String[] args) {
        // 奖品列表,库存一共36
        final List<Award> awardList = new ArrayList<>(4);
        awardList.add(new Award(1, "苹果手机", 7000.0, 0.05, 1));
        awardList.add(new Award(2, "5元金币", 5.0, 0.1, 5));
        awardList.add(new Award(3, "15元天堂雨伞", 15.0, 0.25, 10));
        awardList.add(new Award(4, "谢谢参与", 0.0, 0.6, 20));

        System.out.println("开始抽奖:");
        // 抽奖50次
        for (int i = 0; i < 50; i++) {
            String msg;
            final Award draw = LotteryTool.draw(awardList);
            if (draw == null) {
                msg = "奖品抽完了,下次早点来吧~";
            } else {
                msg = StrUtil.format("抽到了价值「{}」的奖品「{}」", draw.getPrice(), draw.getName());

                // 抽到奖品了,需要减库存,库存不足了,要从列表中剔除
                draw.setStock(draw.getStock() - 1);
                if (draw.getStock() <= 0) {
                    awardList.remove(draw);
                }
            }

            System.out.println(StrUtil.format("第{}次抽奖,结果为:{}", i+1, msg));
        }
        System.out.println("抽奖结束.");
    }
}
/*output~
开始抽奖:
第1次抽奖,结果为:抽到了价值「0.0」的奖品「谢谢参与」
第2次抽奖,结果为:抽到了价值「5.0」的奖品「5元金币」
第3次抽奖,结果为:抽到了价值「0.0」的奖品「谢谢参与」
第4次抽奖,结果为:抽到了价值「0.0」的奖品「谢谢参与」
第5次抽奖,结果为:抽到了价值「0.0」的奖品「谢谢参与」
第6次抽奖,结果为:抽到了价值「0.0」的奖品「谢谢参与」
第7次抽奖,结果为:抽到了价值「15.0」的奖品「15元天堂雨伞」
第8次抽奖,结果为:抽到了价值「0.0」的奖品「谢谢参与」
第9次抽奖,结果为:抽到了价值「15.0」的奖品「15元天堂雨伞」
第10次抽奖,结果为:抽到了价值「5.0」的奖品「5元金币」
第11次抽奖,结果为:抽到了价值「0.0」的奖品「谢谢参与」
第12次抽奖,结果为:抽到了价值「0.0」的奖品「谢谢参与」
第13次抽奖,结果为:抽到了价值「0.0」的奖品「谢谢参与」
第14次抽奖,结果为:抽到了价值「15.0」的奖品「15元天堂雨伞」
第15次抽奖,结果为:抽到了价值「0.0」的奖品「谢谢参与」
第16次抽奖,结果为:抽到了价值「0.0」的奖品「谢谢参与」
第17次抽奖,结果为:抽到了价值「0.0」的奖品「谢谢参与」
第18次抽奖,结果为:抽到了价值「0.0」的奖品「谢谢参与」
第19次抽奖,结果为:抽到了价值「0.0」的奖品「谢谢参与」
第20次抽奖,结果为:抽到了价值「0.0」的奖品「谢谢参与」
第21次抽奖,结果为:抽到了价值「0.0」的奖品「谢谢参与」
第22次抽奖,结果为:抽到了价值「0.0」的奖品「谢谢参与」
第23次抽奖,结果为:抽到了价值「0.0」的奖品「谢谢参与」
第24次抽奖,结果为:抽到了价值「15.0」的奖品「15元天堂雨伞」
第25次抽奖,结果为:抽到了价值「15.0」的奖品「15元天堂雨伞」
第26次抽奖,结果为:抽到了价值「0.0」的奖品「谢谢参与」
第27次抽奖,结果为:抽到了价值「0.0」的奖品「谢谢参与」
第28次抽奖,结果为:抽到了价值「15.0」的奖品「15元天堂雨伞」
第29次抽奖,结果为:抽到了价值「15.0」的奖品「15元天堂雨伞」
第30次抽奖,结果为:抽到了价值「5.0」的奖品「5元金币」
第31次抽奖,结果为:抽到了价值「15.0」的奖品「15元天堂雨伞」
第32次抽奖,结果为:抽到了价值「15.0」的奖品「15元天堂雨伞」
第33次抽奖,结果为:抽到了价值「5.0」的奖品「5元金币」
第34次抽奖,结果为:抽到了价值「15.0」的奖品「15元天堂雨伞」
第35次抽奖,结果为:抽到了价值「5.0」的奖品「5元金币」
第36次抽奖,结果为:抽到了价值「7000.0」的奖品「苹果手机」
第37次抽奖,结果为:奖品抽完了,下次早点来吧~
第38次抽奖,结果为:奖品抽完了,下次早点来吧~
第39次抽奖,结果为:奖品抽完了,下次早点来吧~
第40次抽奖,结果为:奖品抽完了,下次早点来吧~
第41次抽奖,结果为:奖品抽完了,下次早点来吧~
第42次抽奖,结果为:奖品抽完了,下次早点来吧~
第43次抽奖,结果为:奖品抽完了,下次早点来吧~
第44次抽奖,结果为:奖品抽完了,下次早点来吧~
第45次抽奖,结果为:奖品抽完了,下次早点来吧~
第46次抽奖,结果为:奖品抽完了,下次早点来吧~
第47次抽奖,结果为:奖品抽完了,下次早点来吧~
第48次抽奖,结果为:奖品抽完了,下次早点来吧~
第49次抽奖,结果为:奖品抽完了,下次早点来吧~
第50次抽奖,结果为:奖品抽完了,下次早点来吧~
抽奖结束.

Process finished with exit code 0
 */

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值