微信紅包算法探討

今年過年微信紅包成了全民焦點,雖然大多數的紅包就一塊八角的樣子,仍是搞得你們樂此不彼地,蛋爺我年三十晚什麼都沒幹就守在手機旁邊不是搖手機紅包就是搶羣紅包。做爲一名程序猿,天然會想了解下紅包的實現細節。我在網上谷歌了下,微信目前是沒有公佈紅包的實現細節的,因此這裏就提出一個本身的方案。算法

微信紅包規則

紅包領了很多,據觀察紅包主要有如下幾個限制條件:微信

  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/

相關文章
相關標籤/搜索