How WeChat Red‑Packet Random Algorithms Work: Simple Random, Double‑Mean & Segment Split

This article examines the main algorithms behind WeChat's red‑packet distribution—plain random, double‑mean, and segment‑split (including an optimized version), explains their PHP implementations, validates array_rand's randomness, and compares their performance and fairness through code examples and timing tests.

Tencent Cloud Developer
Tencent Cloud Developer
Tencent Cloud Developer
How WeChat Red‑Packet Random Algorithms Work: Simple Random, Double‑Mean & Segment Split

Introduction

The original WeChat red‑packet design evolved from multi‑tiered proportional allocation to pure randomness because users found the former less fun. To avoid early claimers always receiving larger amounts, the random range is adjusted. This article presents three mainstream algorithms, their PHP implementations, and a performance comparison.

1. Plain Random Method

Also called "remaining‑value random", this method selects a random number from the remaining total for each user. It is simple but can produce highly uneven distributions, e.g., the first few users may receive most of the money.

// Remaining‑value random – simple logic, but the random interval shrinks each step, leading to unfairness.
$res = LeftMoneyRedbag($Moneys, $userNums, $isEveryHave);
function LeftMoneyRedbag($Moneys, $userNums, $isEveryHave = 1, $baseMoney = 1) {
    if ($Moneys <= 0 || $userNums <= 0) {
        return ['code'=>-3, 'msg'=>'Invalid amount or user count'];
    }
    if ($isEveryHave && $userNums > $Moneys) {
        return ['code'=>-4, 'msg'=>'Insufficient amount'];
    }
    if ($isEveryHave) {
        $Moneys = $Moneys - ($userNums * $baseMoney);
    }
    $userMoney = [];
    $leftMoneys = $Moneys;
    $leftUserNums = $userNums;
    while ($leftUserNums > 1) {
        $RandVal = 0;
        if ($leftMoneys > 0) {
            $RandVal = mt_rand(0, $leftMoneys);
            $leftMoneys -= $RandVal;
        }
        $userMoney[] = $isEveryHave ? ($baseMoney + $RandVal) : $RandVal;
        $leftUserNums--;
    }
    $userMoney[] = $isEveryHave ? ($baseMoney + $leftMoneys) : $leftMoneys;
    return ['code'=>0, 'msg'=>'success', 'redbag'=>$userMoney];
}

2. Double‑Mean Algorithm

This algorithm uses twice the average of the remaining amount as the upper bound for each random draw, producing a distribution closer to a normal curve. The multiplier can be increased (triple‑mean, etc.) for more randomness.

$Moneys = 99 * 10; // amount in cents
$userNums = 990;
$res = doubleMeanRedbag($Moneys, $userNums);
function doubleMeanRedbag($Moneys, $userNums, $isEveryHave = 1, $baseMoney = 1) {
    if ($Moneys <= 0 || $userNums <= 0) return ['code'=>-3, 'msg'=>'Invalid'];
    if ($isEveryHave && $userNums > $Moneys) return ['code'=>-4, 'msg'=>'Insufficient'];
    if ($isEveryHave) $Moneys -= $userNums * $baseMoney;
    $userMoney = [];
    $leftMoneys = $Moneys;
    $leftUserNums = $userNums;
    while ($leftUserNums > 1) {
        $RandVal = 0;
        if ($leftMoneys > 0) {
            $doubleMeans = ceil($leftMoneys / $leftUserNums * 2);
            $RandVal = mt_rand(0, $doubleMeans);
            $leftMoneys -= $RandVal;
        }
        $userMoney[] = $isEveryHave ? ($baseMoney + $RandVal) : $RandVal;
        $leftUserNums--;
    }
    $userMoney[] = $isEveryHave ? ($baseMoney + $leftMoneys) : $leftMoneys;
    return ['code'=>0, 'msg'=>'success', 'redbag'=>$userMoney];
}

3. Segment‑Split Algorithm

The segment‑split method treats the total amount as a line segment and randomly selects N‑1 cut points to divide it into N parts. A naïve implementation may suffer from cut‑point collisions when the number of users approaches the total amount.

function lineSegmentRedbag($Moneys, $userNums, $isEveryHave = 1, $baseMoney = 1) {
    if ($Moneys <= 0 || $userNums <= 0) return ['code'=>-3, 'msg'=>'Invalid'];
    if ($isEveryHave && $userNums > $Moneys) return ['code'=>-4, 'msg'=>'Insufficient'];
    $cutPoints = [];
    $pointNums = $userNums - 1;
    $userMoney = [];
    while ($pointNums > 0) {
        $randVal = $isEveryHave ? mt_rand(1, $Moneys-1) : mt_rand(0, $Moneys);
        if (in_array($randVal, $cutPoints)) continue; // avoid collision
        $cutPoints[] = $randVal;
        $pointNums--;
    }
    sort($cutPoints);
    $lastVal = 0;
    foreach ($cutPoints as $randPoint) {
        $userMoney[] = $randPoint - $lastVal;
        $lastVal = $randPoint;
    }
    $userMoney[] = $Moneys - $lastVal;
    return ['code'=>0, 'msg'=>'success', 'redbag'=>$userMoney];
}

3.1 Optimized Segment‑Split Using array_rand

To eliminate collision handling, array_rand can pick multiple unique indices at once. The function below demonstrates this optimization.

function lineSegmentOptimize($Moneys, $userNums, $isEveryHave = 1) {
    if ($Moneys <= 0 || $userNums <= 0) return ['code'=>-3, 'msg'=>'Invalid'];
    if ($isEveryHave && $userNums > $Moneys) return ['code'=>-4, 'msg'=>'Insufficient'];
    $MoneysArr = $isEveryHave ? array_fill(1, $Moneys-1, 0) : array_fill(0, $Moneys+1, 0);
    if ($userNums == 1) return ['code'=>0, 'msg'=>'success', 'redbag'=>[$Moneys]];
    $cutPoints = array_rand($MoneysArr, $userNums-1);
    sort($cutPoints);
    $lastVal = 0;
    $userMoney = [];
    foreach ($cutPoints as $randPoint) {
        $userMoney[] = $randPoint - $lastVal;
        $lastVal = $randPoint;
    }
    $userMoney[] = $Moneys - $lastVal;
    return ['code'=>0, 'msg'=>'success', 'redbag'=>$userMoney];
}

4. Verifying array_rand Randomness

Experiments show that array_rand($arr, k) returns k unique indices and the selection covers the full range, including both ends.

function checkRand($range, $num) {
    $statiArr = array_fill(0, 100, 0);
    $sourceArr = range(0, 99);
    for ($i=0;$i<$num;$i++) {
        $indexArr = array_rand($sourceArr, 4);
        foreach ($indexArr as $index) {
            $statiArr[$index]++;
        }
    }
    return $statiArr;
}
$res = checkRand(10, 10000);
var_dump($res);

5. Performance Comparison

A micro‑benchmark runs each algorithm 100 000 times and measures execution time. Results show the optimized segment‑split is faster than the double‑mean method and far quicker than the naïve segment‑split.

function microTime_float(){
    list($usec,$sec)=explode(' ',microtime());
    return ((float)$usec+(float)$sec);
}
$starTime = microTime_float();
for($i=0;$i<100000;$i++){
    lineSegmentRedbag($Moneys,$userNums,$isEveryHave);
    // lineSegmentOptimize($Moneys,$userNums,$isEveryHave);
    // doubleMeanRedbag($Moneys,$userNums,$isEveryHave);
}
$endTime = microTime_float();
$diff = $endTime - $starTime;
echo "Line‑segment time: $diff
";

Graphs (omitted here) illustrate that the naïve segment‑split suffers from high collision rates when the total amount and user count are close, while the optimized version achieves the best performance and a larger random interval than the double‑mean method.

Conclusion

Three red‑packet algorithms each have trade‑offs: plain random is simple but unfair, double‑mean offers a balanced distribution, and segment‑split provides a wide random range; the optimized segment‑split using array_rand combines fairness with high efficiency, making it a strong candidate for production use.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

performancePHPred packetarray_randrandom algorithmsegment split
Tencent Cloud Developer
Written by

Tencent Cloud Developer

Official Tencent Cloud community account that brings together developers, shares practical tech insights, and fosters an influential tech exchange community.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.