本文介绍高并发抢红包系统中,必不可少的红包随机金额生成算法。
假定我们采用预生成方式(相对的还有实时生成,如微信红包),算法的输入和输出如下:
算法要求:
红包随机金额生成算法通常采用二倍均值法,如下是该算法的简介:
如果还想将生成的随机金额列表再随机一点,可以再进行打散:
我们将金额单位设为分,这样的话发红包的总金额和每个人抢到的红包金额都可以精确到1分钱;金额变量整数形式(用元做单位就要用小数了,小数在做计算时容易丢失精度),且确保抢到红包的人至少抢到一分钱。
下面是算法实现。
package com.bobo.springbootredpacket.util;
import com.bobo.springbootredpacket.exception.RedpacketException;
import java.util.List;
import java.util.Random;
import java.util.TreeMap;
import java.util.stream.Collectors;
public class RedpacketUtil {
/**
* 分红包算法:二倍均值法
* @param amount 红包总金额,单位为分
* @param total 总人数
* @return 随机金额数组
*/
public static List<Integer> splitRedpacket(int amount,int total){
// 总金额不能小于总人数(即每人至少一分钱),总人数不能少于1人
if(amount < total || total < 1){
throw new RedpacketException("总金额不能小于总人数(即每人至少一分钱),总人数不能少于1人");
}
// 随机金额数组
int[] amountArr = new int[total];
// 随机数生成器
Random random = new Random();
// 剩余金额和剩余人数
int residualAmount = amount;
int residualTotal = total;
// 总共生成total-1个随机金额
for (int i = 0;i < total-1;i++){
// 金额除以总人数
int max = (residualAmount/residualTotal)*2;
// 本次生成的随机金额,区间:[1,max),不能包含max
int randomAmount = random.nextInt(max-1)+1;
amountArr[i] = randomAmount;
// 剩余金额减少,剩余人数减1
residualAmount -= randomAmount;
residualTotal --;
}
// 为确保红包正好分完,最后一个人的随机金额就是剩余金额
amountArr[amountArr.length-1] = residualAmount;
// 再将生成的随机金额打散:对每个随机金额 对应 生成一个随机数字,然后将这些随机数字排序
TreeMap<Integer, Integer> map = new TreeMap<>();
// 表示第i个随机金额
int i = 0;
while (map.size() < total){
int maxSortNum = random.nextInt(total * 2)+1;
if(map.containsKey(maxSortNum)){
continue;
}
map.put(maxSortNum,i);
i++;
}
// 校验
if(map.size() != total){
throw new RedpacketException("生成的排序号个数不等于总人数");
}
// 替换
List<Integer> amountList = map.values().stream().map(e -> amountArr[e]).collect(Collectors.toList());
return amountList;
}
}
单测:
@Test
public void test(){
// 100元,10个人分
List<Integer> amounts = RedpacketUtil.splitRedpacket(10000, 10);
int sum = 0;
for (int amount : amounts) {
sum += amount;
System.out.println((double) amount/100+"元");
}
System.out.println("抢到的红包金额之和:"+(double)sum/100+"元");
}
单测结果:
12.77元
16.9元
3.08元
11.43元
14.96元
3.85元
6.15元
4.51元
20.07元
6.28元
抢到的红包金额之和:100.0元