微信红包算法探讨

今年过年微信红包成了全民焦点,虽然大多数的红包就一块八角的样子,仍是搞得你们乐此不彼地,蛋爷我年三十晚什么都没干就守在手机旁边不是摇手机红包就是抢群红包。做为一名程序猿,天然会想了解下红包的实现细节。我在网上谷歌了下,微信目前是没有公布红包的实现细节的,因此这里就提出一个本身的方案。算法

微信红包规则

红包领了很多,据观察红包主要有如下几个限制条件:微信

  1. 全部人都能分到红包,也就是不会出现红包数值为0的状况。函数

  2. 全部人的红包数值加起来等于支付的金额。spa

  3. 红包波动范围比较大,约5%~8%的红包数值在平均值的两倍以上,同时数额0.01出现的几率比较高。code

  4. 红包的数值是随机的,而且数值的分布近似于正态分布。orm

这里假设红包的总金额为T,红包个数为k,第i个红包的金额为ai,红包金额生成函数为rand(以后会讨论这个函数)。ip

由于每一个红包的最小值为0.01,因此在初始的时候为每一个红包预留0.01元,那么剩余金额总数为T-0.01*kget

为了让每一个红包金额都是随机的,红包将会由系统逐一辈子成,金额为当前剩余金额范围内的随机数。算法以下:io

ai = rand(T - 0.01 * k - a0 - ... - ai-1)form

正态分布的实现

因为C++等语言提供的随机函数是平均分布的,所以若是须要使红包金额近似正态分布,须要对平均分布进行Box–Muller转换操做,C++实现代码以下:

#define TWO_PI 6.2831853071795864769252866
#include <cmath>
#include <cstdlib>
double generateGaussianNoise(const double mu, const double sigma)
{
    using namespace std;
    static bool haveSpare = false;
    static double rand1, rand2;
 
    if(haveSpare)
    {
        haveSpare = false;
        return (sigma * sqrt(rand1) * sin(rand2)) + mu;
    }
 
    haveSpare = true;
 
    rand1 = rand() / ((double) RAND_MAX);
    if(rand1 < 1e-100) rand1 = 1e-100;
    rand1 = -2 * log(rand1);
    rand2 = (rand() / ((double) RAND_MAX)) * TWO_PI;
 
    return (sigma * sqrt(rand1) * cos(rand2)) + mu;
}

函数generateGaussianNoise的两个参数为指望值mu和标准差sigma,显然,mu的值为当前红包的均值,令分配第i个红包时所剩总金额为Ti,因此:

Ti = T - 0.01 * k - a0 - ... - ai-1

易得:

mu = Ti / (k - i)

sigma的值

红包数额的分布并不彻底符合正太分布,由于每一个红包的数额都有上限和下限,因此准确地说应该是截尾正态分布,在这里红包金额范围为[0, Ti]。

剩下要作的就是肯定sigma的数值,sigma的值会直接影响红包数额的分布曲线。

根据正态分布的三个sigma定理, 生成的随机数值有95.449974%概率落在(mu-2*sigma,mu+2*sigma)内,为了使得mu-2*sigma = 0,sigma = mu/2。对于生成的随机数落在[0, Ti]之外区间的状况,采用截断处理,统一返回0或者Ti。也就是说,最后生成的随机数值分别有大约6%的概率为0或者大于2*mu,加上保留的0.01,符合条件3列出的状况。最后给出这部分C++的代码:

#include <vector>
vector<double> generateMoneyVector(const double mon, const int pics)
{
    vector<double> valueVec;
    double moneyLeft = mon - pics * 0.01;
    double mu, sigma;
    double noiseValue;

    for(int i = 0; i < pics - 1; i++)
    {
        mu = moneyLeft / (pics - i);
        sigma = mu / 2;
        noiseValue = generateGaussianNoise(mu, sigma);
        
        if(noiseValue < 0) noiseValue = 0;
        if(noiseValue > moneyLeft) noiseValue = moneyLeft;
        
        valueVec.push_back(noiseValue + 0.01);
        moneyLeft -= noiseValue;
    }

    return valueVec;
}

对于收到抢红包的请求的时候,只须要进行pop操做并返回便可。

结语

这里还有一些细节没有处理,例如对返回值进行小数位数的处理等,就不作细致说明了。以上只是我对微信红包算法的一种我的猜测,有不足的地方望多指教。

References

  1. http://en.wikipedia.org/wiki/Normal_distribution

  2. http://en.wikipedia.org/wiki/Box%E2%80%93Muller_transform

转载请注明

原文地址:http://danye.me/2015/02/21/wechat-lishi/

相关文章
相关标签/搜索