抽獎算法


抽獎算法理論

在一組獎品中,每個獎品有自己的概率,總概率為 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

實現

首先定義一個有概率的獎品基類,所有繼承這個基類的子類,都可以用調用 LotteryTool.draw 算法(draw 中的參數類型使用了java泛型)

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
 */

參考資料

閱讀原文


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM