题目:实现一个随机抢红包的算法,给定红包总数、红包总额、允许最大金额、允许最小金额,返回全部领到的红包金额情况。
我们可以抽象出一个红包池,指定它的一些属性,开放一个领取随机金额的接口。
太晚了,直接放代码吧!文章金额全部用“分”表示。
1、红包池工厂
用于取代构造函数创建一个红包池,保证其正确性和完整性。
package red_bag;
/**
* 红包池工厂
*/
public class RedBagPoolFactory {
/**
* 创建红包池
*
* @param number 个数
* @param totalMoney 总共金钱(分)
* @param maxMoney 单个最大金钱(分)
* @param minMoney 单个最小金钱(分)
* @return 红包池
*/
public static RedBagPool create(int number, int totalMoney, int maxMoney, int minMoney) {
// 校验
if (number < 1
|| minMoney < 1
|| maxMoney < minMoney
|| maxMoney > totalMoney
|| totalMoney < minMoney * number) {
throw new IllegalArgumentException("存在参数<1或者组合错误!");
}
return new RedBagPool(number, totalMoney, maxMoney, minMoney);
}
}
2、红包池(领域对象)
红包池有属性:总共个数、总共金钱、最大金钱、最小金钱、剩余个数、剩余金钱。
package red_bag;
import java.util.concurrent.ThreadLocalRandom;
/**
* 红包池
*/
public class RedBagPool {
/*× 总共个数 */
private final int totalNumber;
/*× 总共金钱(分) */
private final int totalMoney;
/*× 最大金钱(分) */
private final int maxMoney;
/*× 最小金钱(分) */
private final int minMoney;
/*× 剩余个数 */
private int remainNumber;
/*× 剩余金钱(分) */
private int remainMoney;
public RedBagPool(int totalNumber, int totalMoney, int maxMoney, int minMoney) {
this.totalNumber = this.remainNumber = totalNumber;
this.totalMoney = this.remainMoney = totalMoney;
this.maxMoney = maxMoney;
this.minMoney = minMoney;
}
/**
* 领取
*
* @return 金额(分)
*/
public synchronized int takeRedBag() {
// 数量不足
if (remainNumber <= 0) {
throw new IllegalStateException("红包已经被抢完啦!");
}
// 如果是最后一个,取完全部金钱。否则,根据范围取随机数
int money = remainNumber == 1 ? remainMoney : ThreadLocalRandom.current().nextInt(allowMinMoney(), allowMaxMoney() + 1);
remainMoney -= money;
remainNumber--;
return money;
}
/**
* 允许最小金钱(分)
*
* @return 最小金钱(分)
*/
private int allowMinMoney() {
/*
* 限制:
* 1、不能低于下限
* 2、剩下的人全部领了上限,就是此次能够领的下限
*/
return Math.max(minMoney, remainMoney - (remainNumber - 1) * maxMoney);
}
/**
* 允许最大金钱(分)
*
* @return 最大金钱(分)
*/
private int allowMaxMoney() {
/*
* 限制:
* 1、不能高于上限
* 2、剩下的人全部领了下限,就是此次能够领的上限
*/
return Math.min(maxMoney, remainMoney - (remainNumber - 1) * minMoney);
}
// ---------- 可以加下其它接口,比如已领数量/总共数量、已领金钱/总共金钱 ----------
}
3、红包服务(领域服务)
package red_bag;
import java.util.ArrayList;
import java.util.List;
public class RedBagService {
List<Integer> randomTakeRedBag(int number, int totalMoney, int maxMoney, int minMoney) {
RedBagPool redBagPool = RedBagPoolFactory.create(number, totalMoney, maxMoney, minMoney);
List<Integer> moneyList = new ArrayList<>(number);
for (int i = 0; i < number; i++) {
moneyList.add(redBagPool.takeRedBag());
}
return moneyList;
}
}
4、测试验证
没有使用Junit啥的,别在意哈#_#。同时自动验证实际红包个数、每个金额是否符合条件、每个金额全加起来是否等于总额。
public static void main(String[] args) {
int number = 10, totalMoney = 100, maxMoney = 50, minMoney = 1;
List<Integer> moneyList = new RedBagService().randomTakeRedBag(number, totalMoney, maxMoney, minMoney);
System.out.println("take detail: " + moneyList);
assert moneyList.size() == number;
assert moneyList.stream().allMatch(m -> m >= minMoney && m <= maxMoney);
assert moneyList.stream().mapToInt(Integer::intValue).sum() == totalMoney;
}
take detail: [42, 2, 4, 30, 1, 2, 6, 5, 5, 3]
木有问题,再换一组数据
int number = 5, totalMoney = 1000, maxMoney = 800, minMoney = 10;
take detail: [337, 529, 95, 29, 10]
也是没得问题,打完收工!!!