關於隨機紅包抽獎算法


場景:

  生成10個隨機紅包, 獎池總金額10000, 最小500, 最大1000,獎池全部分配完。

  分析:

  第一想法簡單, 直接生成500-1000之間的隨機數,直接生成10個, 直接上代碼。這種寫法的問題在於最后一個金額生成的時候會出現問題,會有出現超過最大金額的可能性。

  

   /**
     *
     * @param lst 生成的獎項列表
     * @param minAmount 紅包允許的最小金額
     * @param maxAmount 紅包允許的最大金額
     * @param totalAmount 總獎池金額
     * @param count 生成紅包數量
     */public void generateRoundAmount(List<Integer> lst, Integer minAmount, Integer maxAmount, Integer totalAmount, Integer count){
        for (int i = 1; i <= count ; i++) {
            //當前理論允許的最大金額, 保證后續每人持有最小
            Integer tmpMax =  totalAmount - minAmount * (count - i);
            //前4成的最大金額,為理論最大金額的一半, 防止前面金額過大,后面全是1
            tmpMax = i <= Math.round(count*0.4) ? tmpMax/2 : tmpMax;
            //當有傳入最大金額,且小於當前理論最大金額, 則取最大金額,否則取理論最大金額
            tmpMax = maxAmount != null && tmpMax > maxAmount ? maxAmount : tmpMax;
            //當最后一個的時候,全部歸其所有, 否則取隨機數區間[min, max]
            Integer tmpValue = i == count ? totalAmount : StringUtil.getRandomNumberBetween(minAmount, tmpMax); 
            lst.add(tmpValue);
            //減去已抽取金額
            totalAmount = totalAmount - tmpValue;
        }
    }

 

  解決方法有兩種:

   第一種方法,判斷最后一個金額大於maxAmount, 則重新運行,直到出現最后一個金額小於等於maxAmount即可。當然這種方法比較笨, 並不推薦。

   第二中方法,就是剩余的金額, 繼續在已經生成的獎項列表中分配(未超過最大金額的項)。

  改造下方法

  

 /**
     *
     * @param lst 生成的獎項列表
     * @param minAmount 紅包允許的最小金額
     * @param maxAmount 紅包允許的最大金額
     * @param totalAmount 總獎池金額
     * @param count 生成紅包數量
     */
    @Override
    public void generateRoundAmount(List<Integer> lst, Integer minAmount, Integer maxAmount, Integer totalAmount, Integer count){
        Integer remainingAmount = 0; //剩余金額, 默認0
        //判斷下, 紅包溢出和不足都不允許, 等於的情況此處也不考慮
        if(maxAmount * count <= totalAmount || minAmount * count >= totalAmount){
            throw new BaseException("數據錯誤,請調整紅包數量或最大金額上限");
        }
        
        for (int i = 1; i <= count ; i++) {
            //當前理論允許的最大金額, 保證后續每人持有最小
            Integer tmpMax =  totalAmount - minAmount * (count - i);
            //前4成的最大金額,為理論最大金額的一半, 防止前面金額過大,后面全是1 -- 可忽略不要此行
            tmpMax = i <= Math.round(count*0.4) ? tmpMax/2 : tmpMax;
            //當有傳入最大金額,且小於當前理論最大金額, 則取最大金額,否則取理論最大金額
            tmpMax = maxAmount != null && tmpMax > maxAmount ? maxAmount : tmpMax;

            if(i == count && maxAmount != null && totalAmount > maxAmount){
                //最后一個紅包數量大於最大允許金額, 計算出剩余金額
                lst.add(maxAmount);
                remainingAmount = totalAmount - maxAmount;
            } else{
                Integer tmpRandomInt = StringUtil.getRandomNumberBetween(minAmount, tmpMax);
                lst.add(tmpRandomInt);
                //獎池金額為總金額減去已抽取金額
                totalAmount = totalAmount - tmpRandomInt;
            }
        }
        //剩余金額大於0則繼續分配
        while(remainingAmount > 0){
            remainingAmount = addAmountToList(lst, maxAmount, remainingAmount);
        }
    }

    /**
     *
     * @param lst
     * @param maxAmount 允許最大金額
     * @param totalAmount 可分配金額
     */
    private Integer addAmountToList(List<Integer> lst, Integer maxAmount, Integer totalAmount){
        for (int i = 0; i < lst.size(); i++) {
            if (totalAmount <= 0){
                break;
            }
            if (lst.get(i) < maxAmount){ //當列表中的金額小於最大金額時, 才分配
                //臨時最大允許金額
                Integer tmpMax = maxAmount - lst.get(i) > totalAmount ? totalAmount : maxAmount - lst.get(i);
                Integer tmpRandomInt = StringUtil.getRandomNumberBetween(1, tmpMax);
                lst.set(i, lst.get(i) + tmpRandomInt);
                totalAmount = totalAmount - tmpRandomInt;
            }
        }
        return totalAmount;
    }

 

  至此可以解決問題。

  還想到個問題,既然需要循環去分配, 不如再簡化一點,首先給列表分配允許最小額度的紅包, 再循環分配。 繼續改造下此方法

    /**
     *
     * @param lst 生成的獎項列表
     * @param minAmount 紅包允許的最小金額
     * @param maxAmount 紅包允許的最大金額
     * @param totalAmount 總獎池金額
     * @param count 生成紅包數量
     */
    @Override
    public void generateRoundAmount(List<Integer> lst, Integer minAmount, Integer maxAmount, Integer totalAmount, Integer count){
        //判斷下, 紅包溢出和不足都不允許,  等於的情況是允許的
        if(maxAmount * count < totalAmount || minAmount * count > totalAmount){
            throw new BaseException("數據錯誤,請調整紅包數量或最大金額上限");
        }
        //如果最大金額*數量=獎池金額,直接全是最大的,因為獎池最終是要分配完的
        if (maxAmount * count == totalAmount){
            minAmount = maxAmount;
        }
        for (int i = 1; i <= count ; i++) {
            lst.add(minAmount);
        }
        Integer remainingAmount = totalAmount - minAmount * count;
        //剩余金額大於0則繼續分配
        while(remainingAmount > 0){
            remainingAmount = addAmountToList(lst, maxAmount, remainingAmount);
        }
        //此方法可隨機打亂列表排序
        //Collections.shuffle(lst);
    }

 搞定,ps, 此處考慮的是獎池全分配完的情況。

 

  

 


免責聲明!

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



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