場景:
生成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, 此處考慮的是獎池全分配完的情況。