微信抢红包调研

最近突发奇想,对微信抢红的算法产生了兴趣,于是在网上搜了搜,结果发现不少人都跟我有相同的想法,对微信抢红包的算法进行了分析研究。知乎上有篇文章,有个人认为从概率论的角度来说,抢红包的金额大小的分布应该是结尾正态分布(Truncated Normal Distribution),认为大多数人拿到的红包金额数都在均值附近,并给了出了详细的论证,不过看后总觉得不是很有说服力。后来我又看了另外一个人的分析,这个人认为每一个红包的金额大小不会超过剩余金额均值的两倍,并给出了伪代码编写的算法。并给出了他从QCon上面转来的关于微信红包的架构的简单设计的问答。经过对比,我比较赞同这个人的说法。这篇帖子上面还有很多其他人的分析,感兴趣的可以打开下面的链接详细的看一下,里面有很多关于微信红包符合什么样的概率分布的探讨,也有一些回答一看就是错的:https://www.zhihu.com/question/22625187

后来我又研究了一下如果生成符合指定概率分布的随机数,结果在网上发现了一篇不错的文章:http://blog.codinglabs.org/articles/methods-for-generating-random-number-distributions.html ,里面主要介绍了两个方法,一个inverse transform method(逆变换法),还有一个Acceptance-Rejection Method(接受拒绝法),最后一个衍生算法有点复杂没仔细看。研究完这个,我又想到一个问题:java的随机数是怎么生成的呢?带着问题又在网上搜了搜,结果发现java random实际上生成的是一个伪随机数,只要确定了随机因子,随机数的生成序列也就固定了:

Random r1 = new Random(1);
Random r2 = new Random(1);
for(int i=0;i<100;i++){
    System.out.println("-r1:"+i+":"+r1.nextInt());
    System.out.println("-r2:"+i+":"+r2.nextInt());


}
输出结果:
-r1:0:-1155869325
-r2:0:-1155869325
-r1:1:431529176
-r2:1:431529176
-r1:2:1761283695
-r2:2:1761283695
-r1:3:1749940626
-r2:3:1749940626
-r1:4:892128508
-r2:4:892128508
-r1:5:155629808
-r2:5:155629808
-r1:6:1429008869
-r2:6:1429008869
-r1:7:-1465154083
-r2:7:-1465154083
-r1:8:-138487339
-r2:8:-138487339
-r1:9:-1242363800
-r2:9:-1242363800
-r1:10:26273138
-r2:10:26273138
-r1:11:655996946
-r2:11:655996946
-r1:12:-155886662
-r2:12:-155886662
-r1:13:685382526
-r2:13:685382526
-r1:14:-258276172
-r2:14:-258276172
其实这个问题也没有什么大惊效果,大部分java程序员应该都知道,其实我一直也都是知道的,哈哈哈。具体为什么这样,看一下源码就知道了:
protected int next(int bits) {
    long oldseed, nextseed;
    AtomicLong seed = this.seed;
    do {
        oldseed = seed.get();
        nextseed = (oldseed * multiplier + addend) & mask;
    } while (!seed.compareAndSet(oldseed, nextseed));
    return (int)(nextseed >>> (48 - bits));
}
为了防止随机数确定性可能造成的安全性问题,java从jdk7开始提供了一个SecureRandom的随机数生成器。该RNG(random number generator)提供加密的强随机数生成器,要求种子必须是不可预知的,产生非确定性输出。
回到最初的问题,如何实现微信红包的生成器?经过以上的调查,以及自己的想法,最终写出了以下算法:
public static List<BigDecimal> redEnvelopes(BigDecimal value, Integer size){
    List<BigDecimal> envelops = new ArrayList<>();
    BigDecimal cent =new BigDecimal(0.01).setScale(2,RoundingMode.HALF_EVEN);
    BigDecimal mini = value.divide(new BigDecimal(size),2,RoundingMode.UP);
    if(mini.compareTo(cent) ==0){
        for(int i =0;i<size;i++){
            envelops.add(cent);
        }
    }else if(mini.compareTo(cent) <0){
        System.out.println("不可发");
        return null;
    }else {
        Random r = new SecureRandom();
        BigDecimal valueD = value;
        while (size > 0) {
            mini = valueD.divide(new BigDecimal(size),2,RoundingMode.UP);
            if(mini.compareTo(cent) ==0){
                envelops.add(cent);
                valueD = valueD.subtract(cent).setScale(2,RoundingMode.HALF_EVEN);
                size--;
                continue;
            }
            if (size == 1) {
                envelops.add(valueD);
                break;
            }
            //Double seed = 0.5 + Math.sqrt(0.25-0.25*(r.nextDouble()));
            Double seed = r.nextDouble();
            BigDecimal x = new BigDecimal(seed).setScale(2, RoundingMode.HALF_EVEN);
            x = valueD.multiply(x);
            x = x.multiply(new BigDecimal(2));
            x = x.divide(new BigDecimal(size), 2, RoundingMode.HALF_EVEN);
            if(x.compareTo(cent) <0){
                x = cent;
            }
            valueD = valueD.subtract(x).setScale(2,RoundingMode.HALF_EVEN);
            envelops.add(x);
            size--;


        }
        System.out.println("---------------------------");
    }
    return envelops;
}
测试结果如下:
---------------------------
envelop:22.40
envelop:15.79
envelop:12.14
envelop:19.24
envelop:41.74
envelop:31.22
envelop:2.01
envelop:12.57
envelop:41.17
envelop:1.72
sum:200.00
---------------------------
envelop:10.00
envelop:4.22
envelop:14.86
envelop:5.86
envelop:53.92
envelop:27.12
envelop:33.61
envelop:18.48
envelop:27.78
envelop:4.15
sum:200.00
---------------------------
envelop:28.80
envelop:34.62
envelop:15.02
envelop:34.04
envelop:16.92
envelop:2.26
envelop:8.88
envelop:17.44
envelop:9.24
envelop:32.78
sum:200.00
---------------------------
envelop:25.60
envelop:6.59
envelop:26.85
envelop:32.62
envelop:3.97
envelop:15.45
envelop:2.22
envelop:20.81
envelop:46.78
envelop:19.11
sum:200.00
    写完算法发给了几个朋友看了下,在朋友的提示下,发现了微信红包的一个小特点。以一分为单位,发总额m的n个红包,如果m-n=1,那么拿到两份钱的那个人肯定是最后一个抢到红包的人。凭个人感觉,微信在处理这个问题的时候肯定是做了一个例外处理,才会造成这样的结果。以上就是个人对抢红包算法的研究,代码中按照上面提到的生成符合指定概率密度函数的随机数的方法生成随机因子,可以改变红包大小的生成概率。感兴趣的可以深入研究下。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

coming789

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值