1、申 请 I D :whmynb
2、个人邮箱:503268460@qq.com
前言
之前思考一个问题:微信的手气红包是如何实现的
怎么保证了所有人 无先后都有机会成为手气最佳
初次尝试
一开始的想法很简单,就是固定一个奖金池,因为要保证每个最低是有一分钱,所以把随机数的总金额减去剩下每人的0.01作为最高可以获取的红包金额,接着把奖金池减去刚生成的金额,当到最后一人时,直接去把剩余的金额全部给他
具体代码如下(金额的单位为分):
[Java] 纯文本查看 复制代码 public static List<Integer> redPackRand(Integer money,Integer peopleCount){
List<Integer> result=new ArrayList<Integer>();
for(int i=0;i<peopleCount;i++){
if(i==peopleCount-1){//判断是不是最后一个人,是的话直接把所有金额全部给他
// System.out.println("第"+(i+1)+"人,获得:"+money);
result.add(money);
}else {
//取得随机的金额
Integer getMoney =(int)((money-(peopleCount-i-1))*Math.random()+1);
// System.out.println("第"+(i+1)+"人,获得:"+getMoney);
//奖金池中减去取走的部分
money-=getMoney;
// System.out.println("当前剩余:"+money);
result.add(getMoney);
}
}
return result;
}
测试
为了测试出红包算法的情况,我写了一个测试工具,可以循环测试N次,每次M个人,查看第1-M个人可以抢到多少钱的平均数
[Java] 纯文本查看 复制代码 //分别保存进来顺序信息的创建对象
static class PackeAvg{
//保存所有金额数据
List<Integer> list;
public PackeAvg(){
list=new ArrayList<>();
}
//为给列表添加金额
public void addList(Integer a){
list.add(a);
}
public List<Integer> getList() {
return list;
}
//算出平均值
public Integer getAvg() {
if(list.size()!=0){
int sum=0;
for (int num:list){
sum+=num;
}
return sum / list.size();
}
return 0;
}
}
public static void redPackRandAvg(int forCount){
//用于保存次序信息的
List<PackeAvg> avglist=new ArrayList<>();
//循环次数forCount
for(int i =0;i<forCount;i++){
//300块钱10个人抢,单位是分
List<Integer> list = redPackRand(30000, 10);
//标记是第几个人
int j=0;
for (int num:list){
if(avglist.size()<j+1)avglist.add(new PackeAvg());
PackeAvg packeAvg = avglist.get(j);
packeAvg.addList(num);
j++;
}
}
int i=1;
for (PackeAvg avg:avglist){
System.out.println("第"+i+"个人的平均红包为:"+avg.getAvg()+",具体为"+new Gson().toJson(avg));
i++;
}
}
[Java] 纯文本查看 复制代码 public static void main(String[] arg){
//调用20000次,看看评价情况
redPackRandAvg(20000);
}
结果为:
发现使用这种算法,第一次进来的人都是拿的红包大概率都是最大的,而最后抢到的大概率是最小的,而且从平均数可以看到第一个可以拿走奖金池中一半的红包,就算是按照此方法先分出红包存入数组,再让别人随机顺序领也不行,因为微信上没有出现过多人红包可以取走一半的情况(分的少不算啊)
第二次尝试
痛定思痛,因为第一次尝试发现自己想的过于简单了,当抢红包需要规定抢到的红包最大范围,我看到网上有一种是平均数x2的方法,就是100块10个人抢,第一个人可以抢到的是0.01~20块钱之间,而当红包剩下40人数还剩5人的话,下一个人可以拿到的是2*(40/5),就是0.01~16块钱,这样就可以保证红包不会一下子都被拿走,也能控制红包金额大小
为了方便,我这次专门写了一个类
[Java] 纯文本查看 复制代码 public class RedPackeg {
//为总金额
private Integer money;
//剩余金额
private Integer nowMoney;
//红包分了多少份
private Integer packSize;
//取走了多少份
private Integer packNum;
//保存金额信息
private List<Integer> moneyList=new ArrayList<>();
public List<Integer> getMoneyList() {
return moneyList;
}
//构造函数,初始化金额和红包份数
public RedPackeg(Integer money,Integer packSize){
this.money=money;
this.nowMoney=money;
this.packSize=packSize;
this.packNum=0;
}
//取红包,当取完之后返回-1
public Integer getRedPack(){
if(packSize>packNum){
Integer getMoney=(int)(((nowMoney/(packSize-packNum))*2)*Math.random()+1);
//如果getMoney取走后,剩下的钱不够每人0.01的,就把除去没人0.01外的钱都给他
if((nowMoney-getMoney)<(packSize-packNum))getMoney = nowMoney-(packSize+packNum);
packNum++;
nowMoney-=getMoney;
//如果是最后一个人就把金额全部给他
if(packSize==packNum)getMoney+=nowMoney;
moneyList.add(getMoney);
return getMoney;
}
return -1;
}
//测试单次抢红包,能不能保证把红包金额全部发完
//第一次忘记考虑最后一人情况,金额并没有全发完
public static void main(String[] pp){
RedPackeg packeg=new RedPackeg(10000,10);
for (int i=0;i<10;i++){
System.out.println("第"+(i+1)+"个人,获得了"+packeg.getRedPack());
}
List<Integer> list = packeg.getMoneyList();
int sum=0;
for(int a:list){
sum+=a;
}
System.out.println("红包总数:"+sum);
}
}
测试结果,感觉好像达到了自己想要的效果,赶紧循环个几次试试看,看看次序的平均红包金额吧
准备多套了一个方法,为了可以偷懒复用之前的代码
[Java] 纯文本查看 复制代码 public static List<Integer> redPackGet(Integer money,Integer peopleCount){
RedPackeg packeg=new RedPackeg(money,peopleCount);
//从里面取10次钱,记录都会保存在list中
for (int i=0;i<peopleCount;i++)packeg.getRedPack();
return packeg.getMoneyList();
}
public static void redPackAvg(int forCount){
List<PackeAvg> avglist=new ArrayList<>();
for(int i =0;i<forCount;i++){
//只是改变了这里的一个方法
List<Integer> list = redPackGet(30000, 10);
int j=0;
for (int num:list){
if(avglist.size()<j+1)avglist.add(new PackeAvg());
PackeAvg packeAvg = avglist.get(j);
packeAvg.addList(num);
j++;
}
}
int i=1;
for (PackeAvg avg:avglist){
System.out.println("第"+i+"个人的平均红包为:"+avg.getAvg()+",具体为"+new Gson().toJson(avg));
i++;
}
}
public static void main(String[] arg){
redPackAvg(20000);
}
循环了20000次,结果非常的靠谱,基本上可以保证不管是第几个抢红包平均的金额都是差不多的
当循环次数到400000次的时候,金额几乎相差无几
小结
通过上面的代码,已经实现了微信抢红包的基本逻辑,可能和微信真正的红包方法完全不一样,这里只是写出了一个基础部分代码,给大家提供了一个小思路而已
|