前言
微信群发红包,大家都玩过。这边想着自己实现一个微信群发红包分配算法。
红包分配的核心逻辑
1.红包数量严格按照用户输入的数量生成,确保分配的红包数量与用户期望一致。
2.红包金额分配过程中不出现负数,每个红包金额均不小于设定的最小值,保障公平性和合理性。
3.总金额校验确保红包分配后,所有红包金额的总和与用户输入的总金额完全一致,无误差或遗漏。
4.每个红包金额尽量随机
代码实现
package demo.code.utils;
import java.util.Arrays;
import java.util.Random;
public class RedEnvelopeDistributor {
/**
* 分发红包算法
*
* @param totalAmount 总金额(单位:分)
* @param num 红包个数
* @return 每个红包金额的数组(单位:分)
*/
public static int[] distribute(int totalAmount, int num) {
// 如果总金额小于红包个数,抛出异常,避免每个红包金额不足1分
if (totalAmount < num) {
throw new RuntimeException("金额小于红包数");
}
// 每个红包能获取的最小金额是总金额平均值的十分之一
int minAmount = totalAmount / num / 10;
// 确保最小金额至少为1分,避免出现金额为0的情况
if (minAmount < 1) {
minAmount = 1;
}
// 存放每个红包金额的数组
int[] result = new int[num];
// 随机数生成器
Random random = new Random();
// 剩余总金额和剩余红包个数
int remainingAmount = totalAmount;
int remainingNum = num;
// 遍历分配前 n-1 个红包
for (int i = 0; i < num - 1; i++) {
// 计算当前可分配的最大金额,保证其他红包至少能分到 minAmount
int maxAmount = (remainingAmount - remainingNum * minAmount) / 2;
// 如果 maxAmount 小于或等于 minAmount,则直接分配 minAmount
// 否则在 [minAmount, maxAmount] 范围内随机分配
int amount = maxAmount <= minAmount ? minAmount : minAmount + random.nextInt(maxAmount - minAmount + 1);
// 将生成的金额分配给当前红包
result[i] = amount;
// 更新剩余金额和剩余红包个数
remainingAmount -= amount;
remainingNum--;
}
// 最后一个红包分配剩余的全部金额,确保金额总数不变
result[num - 1] = remainingAmount;
// 打乱红包金额顺序,让分配结果看起来更随机
shuffle(result);
// 返回分配后的红包金额数组
return result;
}
/**
* 使用 Fisher-Yates 洗牌算法打乱红包金额顺序
* @param shuffleNums 需要打乱的数组
*/
public static void shuffle(int[] shuffleNums) {
Random random = new Random();
// 遍历数组,逐个交换位置
for (int i = 0; i < shuffleNums.length; i++) {
// 生成一个当前索引到数组末尾范围内的随机索引
final int j = i + random.nextInt(shuffleNums.length - i);
// 交换 i 和 j 位置的值
int temp = shuffleNums[i];
shuffleNums[i] = shuffleNums[j];
shuffleNums[j] = temp;
}
}
public static void main(String[] args) {
int totalAmount = 1000; // 总金额 1000分(10元)
int num = 30; // 红包个数 30个
// 调用分发方法,分配红包
int[] envelopes = distribute(totalAmount, num);
// 输出分配结果(单位:分)
System.out.println("红包分配结果(分):" + Arrays.toString(envelopes));
// 计算并输出红包总金额,验证是否与输入金额一致
System.out.println("总金额(分):" + Arrays.stream(envelopes).sum());
// 将分配结果排序后输出,便于观察红包分布情况
Arrays.sort(envelopes);
System.out.println(Arrays.toString(envelopes));
}
}
运行效果
红包分配结果(分):[3, 24, 10, 4, 12, 188, 4, 7, 4, 190, 3, 23, 42, 4, 96, 9, 3, 64, 54, 3, 3, 10, 95, 25, 86, 3, 3, 19, 3, 6]
总金额(分):1000
[3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 6, 7, 9, 10, 10, 12, 19, 23, 24, 25, 42, 54, 64, 86, 95, 96, 188, 190]
代码分析
minAmount红包最小金额
这边一开始是使用1分钱,就是保证红包最少的金额。后面发现效果不是很好,特别出现比较大的金额数量。这边就做了处理,让最小金额是输入金额平均数的10倍。这样范围会比较均衡一些,不会出现大量1分钱。
maxAmount红包最大金额
// 计算当前可分配的最大金额,保证其他红包至少能分到 minAmount
int maxAmount = (remainingAmount - remainingNum * minAmount) / 2;
当前取得红包金额,会影响后续金额数量。有一个前提是要留给后面足够得金额,不能太少。
amount 红包金额
// 如果 maxAmount 小于或等于 minAmount,则直接分配 minAmount
// 否则在 [minAmount, maxAmount] 范围内随机分配
int amount = maxAmount <= minAmount ? minAmount : minAmount + random.nextInt(maxAmount - minAmount + 1);
这边为了处理输入金额和红包个数接近情况,出现负数,那么直接分配最小金额
Fisher-Yates 洗牌算法
这边加入洗牌算法,是为了让红包更加随机。小红包和大红包得分布,不会挤在一起。
总结
简单实现了红包分配算法,这边也有个问题。就是会出现比较多得最小金额个数,这个大家可以优化这个问题