首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 綜合知識

PHP 加權隨機抽獎演算法實作 (按機率分佈獎品)

     編輯:PHP 綜合知識

應用場景

轉盤抽獎、盲盒開箱、遊戲掉寶、活動營銷等需按設定機率發放獎品的場景。核心需求:獎品列表動態配置、機率精準控制、程式碼可複用、效能高。

核心演算法:前綴和 + 隨機數落區間

  1. 計算所有獎品機率權重總和 $totalWeight。
  2. 產生隨機數 $rand = mt_rand(1, $totalWeight) (或 0 到 total-1)。
  3. 遍歷獎品累加權重 $accumulated += $weight,當 $rand <= $accumulated 時,該獎品中獎。

時間複雜度: O(n) (n 為獎品種類數,通常極小)。若獎品極多可優化為二分搜尋 O(log n)。

通用抽獎工具類

<?php
class LotteryTool {
    private static array $awards = [];
    private static string $weightKey = 'probability'; // 權重欄位名
    private static int $totalWeight = 0;
    private static bool $validated = false;

    public const SUCCESS = 0;
    public const FAIL = -1;

    // 設定權重欄位名 (預設 'probability')
    public static function setWeightKey(string $key): void {
        self::$weightKey = $key;
    }

    // 設定獎品池並驗證
    public static function setAwards(array $awards): void {
        self::$awards = $awards;
        self::validate();
    }

    private static function validate(): void {
        self::$validated = false;
        self::$totalWeight = 0;
        if (empty(self::$awards)) return;
        foreach (self::$awards as $award) {
            if (!isset($award[self::$weightKey]) || $award[self::$weightKey] < 0) {
                return; // 資料格式錯誤
            }
            self::$totalWeight += $award[self::$weightKey];
        }
        if (self::$totalWeight > 0) self::$validated = true;
    }

    // 執行抽獎
    public static function draw(): array {
        if (!self::$validated) {
            return ['code' => self::FAIL, 'msg' => '獎品池未配置或資料無效'];
        }
        if (self::$totalWeight === 0) {
            return ['code' => self::FAIL, 'msg' => '總權重為 0'];
        }

        $rand = mt_rand(1, self::$totalWeight); // 1 ~ 總權重
        $accumulated = 0;
        
        foreach (self::$awards as $key => $award) {
            $accumulated += $award[self::$weightKey];
            if ($rand <= $accumulated) {
                return [
                    'code' => self::SUCCESS,
                    'award_key' => $key,
                    'award_info' => $award,
                    'msg' => '中獎'
                ];
            }
        }
        // 理論上不會執行到這裡
        return ['code' => self::FAIL, 'msg' => '邏輯錯誤'];
    }
}
?>

使用範例與機率驗證

<?php
// 1. 定義獎品池 (權重欄位 'pro')
$awards = [
    'grand_prize' => ['pro' => 1,  'info' => '大獎: iPhone 15 (1%)'],
    'first_prize' => ['pro' => 5,  'info' => '一等獎: 紅包 100元 (5%)'],
    'second_prize'=> ['pro' => 20, 'info' => '二等獎: 優惠券 20元 (20%)'],
    'third_prize' => ['pro' => 74, 'info' => '三等獎: 積分 100分 (74%)'],
    // 'no_prize'    => ['pro' => 0, 'info' => '未中獎'], // 權重 0 可不寫或寫 0
];

// 2. 配置工具
LotteryTool::setWeightKey('pro');
LotteryTool::setAwards($awards);

// 3. 模擬 100,000 次抽獎驗證機率
$stats = [];
for ($i = 0; $i < 100000; $i++) {
    $result = LotteryTool::draw();
    if ($result['code'] === LotteryTool::SUCCESS) {
        $key = $result['award_key'];
        $stats[$key] = ($stats[$key] ?? 0) + 1;
    }
}

// 4. 輸出統計
echo "獎品\t\t設定機率\t實際次數\t實際機率\n";
foreach ($awards as $key => $award) {
    $count = $stats[$key] ?? 0;
    $actual = ($count / 100000) * 100;
    echo "{$award['info']}\t{$award['pro']}%\t\t{$count}\t\t{$actual}%\n";
}

/*
輸出範例:
大獎: iPhone 15 (1%)		1%		1002		1.002%
一等獎: 紅包 100元 (5%)	5%		4987		4.987%
二等獎: 優惠券 20元 (20%)	20%		19956		19.956%
三等獎: 積分 100分 (74%)	74%		74055		74.055%
*/
?>

進階擴充建議

1. 防刷/風控整合

  • 抽獎前檢查用戶當日次數 (Redis 計數器)。
  • 記錄抽獎日誌 (用戶ID、IP、獎品、時間) 供事後稽核。
  • 大獎二次驗證 (人工審核/手機驗證碼)。

2. 庫存控制 (獎品有限量)

抽獎前用 Redis Lua 腳本 原子檢查並扣減庫存,庫存為 0 則從獎品池移除或設權重為 0。

3. 二分搜尋優化 (獎品超過 100 種)


// 建立前綴和陣列 [w1, w1+w2, w1+w2+w3, ...]
$prefixSums = [];
$sum = 0;
foreach ($awards as $a) { $sum += $a['pro']; $prefixSums[] = $sum; }

// 二分搜尋
$rand = mt_rand(1, $sum);
$idx = 0;
$low = 0; $high = count($prefixSums) - 1;
while ($low <= $high) {
    $mid = ($low + $high) >> 1;
    if ($prefixSums[$mid] < $rand) $low = $mid + 1;
    else { $idx = $mid; $high = $mid - 1; }
}
$winner = array_keys($awards)[$idx];

4. 保底機制 (Pity System)

記錄用戶連續未中大獎次數,達閾值時強制提升大獎權重或直接發放。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved