Java实现微信抢红包
微信抢红包的实现原理与Java实现
摘要:
微信抢红包功能是微信社交平台的一个重要特色之一。本文将以Java语言为基础,详细介绍微信抢红包的实现原理以及如何用Java来实现这一功能。
1. 引言
微信抢红包是一种在线社交红包活动,参与者可以通过微信平台向群聊中的其他成员发送虚拟红包,然后其他成员可以抢夺这些红包。微信抢红包功能的实现是利用互联网技术和算法,结合微信的消息推送功能,实现红包的发放和抢夺。
2. 功能实现
2.1 生成红包
在微信抢红包功能中,参与者可以设置红包的金额、个数和其他的条件。首先,需要根据这些条件计算出每个红包的金额,然后将这些金额进行封装,生成一个红包对象。
2.2 拆分红包
接下来,需要将生成的红包进行拆分,即将红包金额分配到不同的子红包中。拆分红包的算法通常使用二倍均值法,即将红包总金额平均分配到每个子红包中,但每个子红包的金额要小于原红包的一半。
2.3 发送红包
一旦红包被拆分,就可以使用微信的消息推送功能将这些子红包发送到群聊中。
2.4 抢夺红包
当红包发送到群聊中后,其他成员就可以开始抢红包了。每个成员在群聊中通过点击红包进行抢夺。当一个成员抢到红包后,就需要将抢到的红包金额从原来的红包中扣除,并将剩余的红包再次拆分成子红包发送到群聊中。抢夺红包的算法通常使用的是随机法或者二倍均值法。
3. 技术实现
3.1 使用Java语言实现微信抢红包功能可以借助Java的面向对象特性来实现。首先,可以定义一个红包类,包括红包的金额、个数和其他属性以及相关的方法。然后,可以通过计算和算法来生成和拆分红包。
3.2 使用Java的集合类如ArrayList来存储拆分后的红包金额,这样方便后续的发送和抢红包操作。可以使用Thread类来创建多线程,实现红包的发送和抢夺。
3.3 可以利用微信提供的开发接口来实现红包的发送和抢夺。通过调用微信提供的API,可以实现红包的发送和抢夺功能。
4. 总结
微信抢红包功能的实现需要通过互联网技术和算法来实现红包的生成、拆分、发送和抢夺等操作。Java作为一门流行的面向对象编程语言,提供了丰富的类库和工具来简化开发过程。通过合理的设计和利用Java的相关技术,可以实现一个功能完整的微信抢红包系统。
抢红包的这个问题,最最开始关注是因为阿里的场景面试题提到过的
当时的代码处理还很简单,先从普通场景探索下红包问题
拼手气红包--线性切割法
场景:100块钱红包,群内50人,红包数量为20个,30个人将抢不到红包
这个算法可以把总金额想象成一条线段,每个人都有机会切一刀,前面的人切完(有失公平性,会出现第一个切一大段的情况,后面需要改造),剩下的后面的人再接着切剩下的,这样越是前面的人截取的长度(理解成领取到的红包金额)越大的概率就越大。
//线段切割法public static void lineSegmentCutting(double money, int number) {if (money < 0 && number < 1) System.out.println("输入错误!");double begin = 0, end = money;double y = 0; List<Double> list = new ArrayList<>();for (int i = 0; i < number - 1; i++) {double nn = 0;double amount = nextDouble(begin, end);
nn = amount - begin; System.out.println("第" + (i + 1) + "个人领取的红包金额为:" + format(nn)); y += nn; begin = amount; list.add(nn); } System.out.println("第" + number + "个人领取的红包金额为:" + format(end - begin)); Double max = Collections.max(list); System.out.println("线段切割法手气最佳!!!" + format(max)); y += (end - begin); System.out.println("验证发出的红包总金额为:" + format(y)); }
第1个人领取的红包金额为:148.49第2个人领取的红包金额为:1.16第3个人领取的红包金额为:12.14第4个人领取的红包金额为:27.32第5个人领取的红包金额为:3.76第6个人领取的红包金额为:5.34第7个人领取的红包金额为:1.28第8个人领取的红包金额为:0.39第9个人领取的红包金额为:0.09第10个人领取的红包金额为:0.01第11个人领取的红包金额为:0.01第12个人领取的红包金额为:0.00第13个人领取的红包金额为:0.00第14个人领取的红包金额为:0.00第15个人领取的红包金额为:0.00第16个人领取的红包金额为:0.00第17个人领取的红包金额为:0.00第18个人领取的红包金额为:0.00第19个人领取的红包金额为:0.00第20个人领取的红包金额为:0.00线段切割法手气最佳!!!148.49验证发出的红包总金额为:200.00
问题:如何保证20个人都能抢到红包?
2.二倍均值法
这是一种很合理很公平的抢红包算法了
在此我们假设
红包剩余金额为 M
红包剩余数量为 N
这种算法就是每次都在区间[0,M/N×2] 随机取一个数
假设100元红包发10个人,那么合理的做法应该是每个人领到10元的概率相同。
第一个人随机金额的范围为[0,100/10×2] ,也就是[0,20],这样平均可以领到10元,此时剩余金额为100-10=90。
第二个人随机金额的范围为[0,90/9×2] ,也就是[0,20],这样平均也可以领到10元,此时剩余金额为90-10=80。
第三个人随机金额的范围为[0,80/8×2] ,也就是[0,20],这样平均也可以领到10元。
//二倍均值法 public static List doubleMeanMethod(double money, int number) { List result = new ArrayList(); if (money < 0 && number < 1) return null; double amount, sum = 0; int remainingNumber = number; int i = 1; List<Double> list = new ArrayList<>(); while (remainingNumber > 1) { amount = nextDouble(0.01, 2 * (money / remainingNumber)); sum += amount; System.out.println("第" + i + "个人领取的红包金额为:" + format(amount)); money -= amount; remainingNumber--; result.add(amount); i++; list.add(amount); } result.add(money); System.out.println("第" + i + "个人领取的红包金额为:" + format(money)); Double max = Collections.max(list); System.out.println("二倍均值法手气最佳!!!" + format(max)); sum += money; System.out.println("验证发出的红包总金额为:" + format(sum));
return result; }
运行结果:
请输入红包总金额:200请输入红包数量:20第1个人领取的红包金额为:0.48第2个人领取的红包金额为:16.39第3个人领取的红包金额为:7.44第4个人领取的红包金额为:15.50第5个人领取的红包金额为:15.48第6个人领取的红包金额为:0.82第7个人领取的红包金额为:0.05第8个人领取的红包金额为:19.11第9个人领取的红包金额为:2.67第10个人领取的红包金额为:0.55第11个人领取的红包金额为:13.09第12个人领取的红包金额为:23.05第13个人领取的红包金额为:7.78第14个人领取的红包金额为:10.44第15个人领取的红包金额为:19.88第16个人领取的红包金额为:0.92第17个人领取的红包金额为:12.22第18个人领取的红包金额为:12.73第19个人领取的红包金额为:3.06第20个人领取的红包金额为:18.33二倍均值法手气最佳!!!23.05验证发出的红包总金额为:200.00
3.等值红包
场景:红包总额为100,群内人数为20人,需要每个人都为5块钱
/* * 等值红包 * */ public static void average(double money, int number) { BigDecimal count = BigDecimal.valueOf(money); if (money < 0 && number < 1) System.out.println("输入错误!"); BigDecimal y =new BigDecimal(0) ; BigDecimal num = BigDecimal.valueOf(number); for (int i = 0; i < number ; i++) { BigDecimal divide = count.divide(num,2,BigDecimal.ROUND_HALF_UP); System.out.println("第" + (i + 1) + "个人领取的红包金额为:" + divide); y=y.add(divide); } System.out.println("等值红包验证发出的红包总金额为:" + y); }
运行结果:
第1个人领取的红包金额为:5.00第2个人领取的红包金额为:5.00第3个人领取的红包金额为:5.00第4个人领取的红包金额为:5.00第5个人领取的红包金额为:5.00第6个人领取的红包金额为:5.00第7个人领取的红包金额为:5.00第8个人领取的红包金额为:5.00第9个人领取的红包金额为:5.00第10个人领取的红包金额为:5.00第11个人领取的红包金额为:5.00第12个人领取的红包金额为:5.00第13个人领取的红包金额为:5.00第14个人领取的红包金额为:5.00第15个人领取的红包金额为:5.00第16个人领取的红包金额为:5.00第17个人领取的红包金额为:5.00第18个人领取的红包金额为:5.00第19个人领取的红包金额为:5.00第20个人领取的红包金额为:5.00等值红包验证发出的红包总金额为:100.00
但问题是如果200块,发214人,出现小数时需要保留2位小数出现精度损失导致总额不够200块如何解决?
ps:报错
BigDecimal divide = count.divide(num);
java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result
原因:JAVA中如果用BigDecimal做除法的时候一定要在divide方法中传递第二个参数,定义精确到小数点后几位,否则在不整除的情况下,结果是无限循环小数时,就会抛出以上异常
改为以上的
BigDecimal divide = count.divide(num,2,BigDecimal.ROUND_HALF_UP);
第212个人领取的红包金额为:0.93第213个人领取的红包金额为:0.93第214个人领取的红包金额为:0.93等值红包验证发出的红包总金额为:199.02
那么出现不能整除的小数时,即便是BigDecimal 进行计算,保留2位小数后仍有精度损失,那么微信是如何解决的?
微信直接变更场景
将每个红包的金额由用户定义
public static void averageByCustom(double money, int number) { System.out.println("红包总金额为:" + format(money*number)); }
此处加format,不加的话需要转为BigDecimal进行计算,精度计算会保留超过既定金额
完整代码
import java.math.BigDecimal;import java.util.*;
/** * 生成min到max范围的浮点数 **/public class redEnvelope { public static double nextDouble(final double min, final double max) { return min + ((max - min) * new Random().nextDouble()); }
public static String format(double value) {
return new java.text.DecimalFormat("0.00").format(value); // 保留两位小数 }
//二倍均值法 public static List doubleMeanMethod(double money, int number) { List result = new ArrayList(); if (money < 0 && number < 1) return null; double amount, sum = 0; int remainingNumber = number; int i = 1; List<Double> list = new ArrayList<>(); while (remainingNumber > 1) { amount = nextDouble(0.01, 2 * (money / remainingNumber)); sum += amount; System.out.println("第" + i + "个人领取的红包金额为:" + format(amount)); money -= amount; remainingNumber--; result.add(amount); i++; list.add(amount); } result.add(money); System.out.println("第" + i + "个人领取的红包金额为:" + format(money)); Double max = Collections.max(list); System.out.println("二倍均值法手气最佳!!!" + format(max)); sum += money; System.out.println("验证发出的红包总金额为:" + format(sum));
return result; }
//线段切割法 public static void lineSegmentCutting(double money, int number) { if (money < 0 && number < 1) System.out.println("输入错误!"); double begin = 0, end = money; double y = 0; List<Double> list = new ArrayList<>(); for (int i = 0; i < number - 1; i++) { double nn = 0; double amount = nextDouble(begin, end);
nn = amount - begin; System.out.println("第" + (i + 1) + "个人领取的红包金额为:" + format(nn)); y += nn; begin = amount; list.add(nn); } System.out.println("第" + number + "个人领取的红包金额为:" + format(end - begin)); Double max = Collections.max(list); System.out.println("线段切割法手气最佳!!!" + format(max)); y += (end - begin); System.out.println("验证发出的红包总金额为:" + format(y)); }
/* * 等值红包 * */
public static void average(double money, int number) { BigDecimal count = BigDecimal.valueOf(money); if (money < 0 && number < 1) System.out.println("输入错误!"); BigDecimal y =new BigDecimal(0) ; BigDecimal num = BigDecimal.valueOf(number); for (int i = 0; i < number ; i++) { BigDecimal divide = count.divide(num,2,BigDecimal.ROUND_HALF_UP); System.out.println("第" + (i + 1) + "个人领取的红包金额为:" + divide); y=y.add(divide); } System.out.println("等值红包验证发出的红包总金额为:" + y); }
/* * 等值红包用户定义单个金额大小 * */ /** * 功能描述: * @Param: [money:单个金额, number:红包个数] * @Return: void * @Author: zhaozhenkun * @Date: 2021/2/22 11:27 */ public static void averageByCustom(double money, int number) {
System.out.println("红包总金额为:" + format(money*number)); }
/** * 拆红包 * @param price 红包总金额 * @param person 红包个数 */ public static void grapRed(int price,int person){ List<BigDecimal> moneys = math(BigDecimal.valueOf(price), person); if (moneys != null) { BigDecimal b = new BigDecimal(0); int num = 1; for (BigDecimal bigDecimal : moneys) { System.out.println("第" + num + "个人抢到:" + bigDecimal + "元 "); b = b.add(bigDecimal); num++; } for (int i = 0;i < moneys.size(); i++) { BigDecimal bigDecimal = moneys.get(i); if (bigDecimal.equals(Collections.max(moneys))) { System.out.println("最大金额是第"+(i + 1)+"个人," + "最佳手气:" + Collections.max(moneys)); } } //验证红包总值 System.out.println("红包总额:" + b + "元 "); } } /** * 单个红包0.01 * @param redPrice 红包总额 * 人数 * @return */ public static List<BigDecimal> math(BigDecimal redPrice, int personNumber) { //发红包最少0.01*personNumber if (redPrice.doubleValue() < personNumber * 0.01) { return null; } Random random = new Random();
int money = redPrice.multiply(BigDecimal.valueOf(100)).intValue(); double count = 0;
double[] arrRandom = new double[personNumber];
List<BigDecimal> arrMoney = new ArrayList<BigDecimal>(personNumber);
for (int i = 0; i < arrRandom.length; i++) { int r = random.nextInt((personNumber) * 99) + 1; count += r; arrRandom[i] = r; } int c = 0; for (int i = 0; i < arrRandom.length; i++) {
Double x = new Double(arrRandom[i] / count);
int m = (int) Math.floor(x * money);
if (m == 0) { m = 1; } c += m;
if (i < arrRandom.length - 1) { arrMoney.add(new BigDecimal(m).divide(new BigDecimal(100))); } else {
arrMoney.add(new BigDecimal(money - c + m).divide(new BigDecimal(100))); } }
Collections.shuffle(arrMoney); return arrMoney; }
public static void main(String[] args) { Scanner sc = new Scanner(System.in); System.out.println("这是一段模拟抢红包的代码。");
/* int number; double money; System.out.print("请输入红包总金额:"); money = sc.nextDouble(); System.out.print("请输入红包数量:"); number = sc.nextInt();*/ //System.out.println(money + " " + number);
/* //二倍均值法 doubleMeanMethod(money, number); //System.out.println(doubleMeanMethod(money,number).toString()); System.out.println();
//线段切割法 lineSegmentCutting(money, number); System.out.println(); average(money,number);*/ // averageByCustom(money, number);
grapRed(100,20); }
}
抢红包的实际面试题,本公众号的阿里面试题
题目:写一个发红包程序,连续发N次红包(每次红包总金额相同),每个红包随机分给M个人
要求
(1)最大红包金额不能超过红包总金额的90%;
(2)连续N次发红包,获得最佳手气(红包金额最高)的人,得到最佳手气的次数不超过总次数的30%;
(3)单个红包最小1分钱;
1.控制最大红包金额
2.控制最佳手气人次数
3.最小包1分钱
抢红包数据线性分析
可以参考下抢红包的大数据分析,根据抢红包的线性分布来参考下最公平的算法
1最公平的算法是二倍均值算法,能够避免第N个过大或过小问题
或者算出平均值,在平均值上加钱
2.如果出现两个相同大小金额如何处理?
如果存在相同最大金额则重新计算
3.如果全部处理完还剩余钱怎么办?
将钱加到小红包里
4.如果钱是负数怎么办?
从小红包里将钱抽取出来
/** * 拆红包 * @param price 红包总金额 * @param person 红包个数 */ public static void grapRed(int price,int person){ List<BigDecimal> moneys = math(BigDecimal.valueOf(price), person); if (moneys != null) { BigDecimal b = new BigDecimal(0); int num = 1; for (BigDecimal bigDecimal : moneys) { System.out.println("第" + num + "个人抢到:" + bigDecimal + "元 "); b = b.add(bigDecimal); num++; } for (int i = 0;i < moneys.size(); i++) { BigDecimal bigDecimal = moneys.get(i); if (bigDecimal.equals(Collections.max(moneys))) { System.out.println("最大金额是第"+(i + 1)+"个人," + "最佳手气:" + Collections.max(moneys)); } } //验证红包总值 System.out.println("红包总额:" + b + "元 "); } } /** * 单个红包0.01 * @param redPrice 红包总额 * 人数 * @return */ public static List<BigDecimal> math(BigDecimal redPrice, int personNumber) { //发红包最少0.01*personNumber if (redPrice.doubleValue() < personNumber * 0.01) { return null; } Random random = new Random();
int money = redPrice.multiply(BigDecimal.valueOf(100)).intValue(); double count = 0;
double[] arrRandom = new double[personNumber];
List<BigDecimal> arrMoney = new ArrayList<BigDecimal>(personNumber);
for (int i = 0; i < arrRandom.length; i++) { int r = random.nextInt((personNumber) * 99) + 1; count += r; arrRandom[i] = r; } int c = 0; for (int i = 0; i < arrRandom.length; i++) {
Double x = new Double(arrRandom[i] / count);
int m = (int) Math.floor(x * money);
if (m == 0) { m = 1; } c += m;
if (i < arrRandom.length - 1) { arrMoney.add(new BigDecimal(m).divide(new BigDecimal(100))); } else {
arrMoney.add(new BigDecimal(money - c + m).divide(new BigDecimal(100))); } }
Collections.shuffle(arrMoney); return arrMoney; }
运行结果:100块.20个人抢
第1个人抢到:5.76元 第2个人抢到:3.89元 第3个人抢到:6.7元 第4个人抢到:6.35元 第5个人抢到:4.03元 第6个人抢到:2.55元 第7个人抢到:3元 第8个人抢到:8.13元 第9个人抢到:4.07元 第10个人抢到:0.4元 第11个人抢到:8.08元 第12个人抢到:3.91元 第13个人抢到:1.65元 第14个人抢到:8.92元 第15个人抢到:6.5元 第16个人抢到:4.69元 第17个人抢到:4.26元 第18个人抢到:1.56元 第19个人抢到:8.86元 第20个人抢到:6.69元 最大金额是第14个人,最佳手气:8.92红包总额:100.00元
找大佬请教的代码
import org.apache.commons.lang3.RandomUtils;
import java.util.*;
public class SimpleRedRnvelopes {
private Map<Integer, Integer[]> mPriceMapper = new HashMap<>(); private Map<Integer, Integer> top = new HashMap<>(); private Map<Integer,Integer> everyTop = new HashMap<>();
public int getRandomForIntegerBounded(int min, int max) { int intBounded = RandomUtils.nextInt(min, max); return intBounded; }
public SimpleRedRnvelopes(double mRedRnvelopesPrice, int m, int n) { if (m <= 0 || n <= 0 || m < 2 || (mRedRnvelopesPrice * 100 < m)) { throw new IllegalArgumentException(); } //总抢红包金额(分),每个人默认已经抢到了1分 一定为整数 int price = (int) (mRedRnvelopesPrice * 100 - m); //剩余金额、按分取整。保留整数部分 如果超过了整数部分 那么一定是超额了 int maxPrice = (int) (mRedRnvelopesPrice * 100 * 0.9); System.out.println("可抢总金额:" + price + " 单人最大金额: " + maxPrice); for (int i = 0; i < n;) { Integer priceList[] = new Integer[m]; int surplus = price; //剩余 for (int j = 0; j < m - 1; ) { if (surplus == 0) { priceList[j] = 1; } else { int snagging = genPrice(surplus, m, maxPrice).get(getRandomForIntegerBounded(0, m));// int snagging = getRandomForIntegerBounded(0,surplus + 1); if (snagging > (maxPrice - 1)) { continue; } surplus -= snagging; priceList[j] = snagging + 1; } j++; } priceList[m - 1] = surplus + 1; //获取手气最佳 int max = Arrays.asList(priceList).stream().mapToInt(x -> x).max().getAsInt(); int passengerIndex = getMaxIndex(priceList,max); if(top.containsKey(passengerIndex)){ if((top.get(passengerIndex) + 1) > n * 0.3){ continue; } Integer topCount = top.get(passengerIndex); topCount = new Integer(topCount + 1); }else { top.put(passengerIndex,1); } i++; mPriceMapper.put(i, priceList); everyTop.put(i,passengerIndex); } }
public int getMaxIndex(Integer[] list,int max){ for(int i = 0;i < list.length;i++){ if(list[i] == max){ return i; } } throw new RuntimeException(); }
/** * 考虑到第一个人抢到到金额概率永远大于后面的人(金额大小结果随着人数依次递减),简单的随机数算法 * @return */ public List<Integer> genPrice(int price, int m, int maxPrice) { List<Integer> priceList = new ArrayList(); int surplus = price; int max = 0; //不能同时存在那个相同的最大值,存在相同的最大值需要重算 for (int j = 0; j < m;) { int snagging = getRandomForIntegerBounded(0, surplus + 1); if (snagging > (maxPrice - 1)) { continue; } surplus -= snagging; priceList.add(snagging); j++; } Collections.shuffle(priceList); return priceList; }
public void print() { for (Map.Entry<Integer, Integer[]> c : mPriceMapper.entrySet()) { int sum = Arrays.asList(c.getValue()).stream().mapToInt(x -> x).sum(); Integer key = c.getKey(); System.out.println(c.getKey() + " 金额数据:" + Arrays.asList(c.getValue()) + " Sum: " + sum + " 手气最佳为第:" + everyTop.get(c.getKey())+"人"); } } public static void main(String[] args) { //金额,人数,次数 new SimpleRedRnvelopes(100, 10, 1).print(); }
}
运行结果:
1 金额数据:[2, 4, 13, 1, 14, 2793, 1, 120, 1, 7051] Sum: 10000 手气最佳为第:9人2 金额数据:[2488, 495, 22, 2, 5493, 663, 8, 1, 7, 821] Sum: 10000 手气最佳为第:4人3 金额数据:[2240, 5, 40, 110, 1, 4695, 2451, 9, 1, 448] Sum: 10000 手气最佳为第:5人4 金额数据:[18, 773, 2535, 1, 4, 2376, 15, 111, 1710, 2457] Sum: 10000 手气最佳为第:2人5 金额数据:[1153, 5333, 169, 4, 364, 159, 3, 98, 204, 2513] Sum: 10000 手气最佳为第:1人
参考文章
https://www.meipian.cn/cea2ksx 抢红包大数据分析https://www.zhihu.com/question/22625187?sort=created 微信的红包算法 https://blog.csdn.net/paincupid/article/details/82054647 带红包上下限的算法https://www.cnblogs.com/rutaha/p/14054156.html 抢红包算法
参考文献:
1. https://www.ibm.com/developerworks/cn/java/j-lo-wechat-red-packets/index.html
2. https://www.yiibai.com/java/java_wechat_red_pocket.html
- 点赞
- 收藏
- 关注作者
评论(0)