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

PHP 實作加權隨機抽獎演算法完整教學與代碼範例

     編輯:PHP 相關知識

PHP 實作加權隨機抽獎演算法完整教學與代碼範例

在開發抽獎、轉盤、隨機廣告等功能時,常需要根據權重機率來決定中獎結果。本文將詳細介紹如何使用 PHP 實作一個靈活、可擴展的加權隨機抽獎系統,包含核心邏輯解析、物件導向設計與完整可執行代碼。

核心邏輯:權重區間映射法

加權隨機的基本原理是將各獎品的權重累加形成總權重 N,然後在 1 ~ N 之間取一個隨機數 M,最後判斷 M 落在哪個獎品的權重區間內,該獎品即為中獎結果。

範例場景

  • 獎品 1:權重 5
  • 獎品 2:權重 6
  • 獎品 3:權重 7
  • 獎品 4:權重 2

總權重 N = 5 + 6 + 7 + 2 = 20。隨機數範圍為 1~20。區間劃分如下:

  • 獎品 1:1 ~ 5
  • 獎品 2:6 ~ 11
  • 獎品 3:12 ~ 18
  • 獎品 4:19 ~ 20

若隨機數為 14,則落在獎品 3 的區間,中獎結果為獎品 3。

物件導向實作

為了方便擴展與維護,我們將抽獎邏輯封裝為三個類別:

  • Prize:獎品實體,包含 ID、權重、名稱及區間範圍
  • PrizePoll:獎品池,管理獎品集合,支援新增、移除、更新、篩選可用獎品
  • SimpleTurn:抽獎執行器,負責計算區間、產生隨機數、判斷中獎結果

完整代碼實作

<?php
/**
 * 獎品實體類別
 */
class Prize {
    public $id = null;
    public $weight = null;
    public $name = null;
    protected $start = 0;
    protected $end = 0;

    public function __construct($id, $weight, $name) {
        if (!$id) {
            throw new Exception('獎品ID不可為空。');
        }
        $this->id = $id;
        $this->weight = $weight ?: 0;
        $this->name = $name ?: '隨機獎品' . $id;
    }

    public function getId() { return $this->id; }
    public function getWeight() { return $this->weight; }

    public function setRange($start, $end) {
        $this->start = $start;
        $this->end = $end;
    }

    public function inRange($num) {
        return ($num >= $this->start) && ($num <= $this->end);
    }
}

/**
 * 獎品池類別,實作 IteratorAggregate 與 Countable 介面
 */
class PrizePoll implements IteratorAggregate, Countable {
    protected $items = [];

    public function addItem(Prize $item) {
        $this->items[$item->getId()] = $item;
        return $this;
    }

    public function removeItem($itemId) {
        if (isset($this->items[$itemId])) {
            unset($this->items[$itemId]);
        }
        return $this;
    }

    public function updateItem(Prize $item) {
        if (isset($this->items[$item->getId()])) {
            $this->items[$item->getId()] = $item;
        }
        return $this;
    }

    public function getItems() { return $this->items; }

    public function getVisibleItems() {
        $items = [];
        foreach ($this->items as $item) {
            if ($item->getWeight()) {
                $items[$item->getId()] = $item;
            }
        }
        return $items;
    }

    public function count() { return count($this->items); }

    public function getIterator() { return new ArrayIterator($this->items); }
}

/**
 * 簡易抽獎執行類別
 */
class SimpleTurn {
    protected $poll = null;

    public function __construct(PrizePoll $poll = null) {
        if ($poll) $this->setPoll($poll);
    }

    public function run(PrizePoll $poll = null) {
        $poll = $poll ?: $this->poll;
        if (!$poll) throw new Exception('獎池未初始化');
        if ($poll->count() <= 0) throw new Exception('獎池為空');

        $items = $poll->getVisibleItems();
        if (count($items) <= 0) throw new Exception('無可用獎品(權重皆為 0)');

        $sum = 0;
        foreach ($items as $item) {
            $start = $sum + 1;
            $sum += $item->getWeight();
            $end = $sum;
            $item->setRange($start, $end);
        }

        $rand = $this->getRandNum(1, $sum);
        foreach ($items as $item) {
            if ($item->inRange($rand)) {
                return $item;
            }
        }
        return null;
    }

    protected function getRandNum($min, $max) {
        return mt_rand($min ?: 1, $max);
    }

    public function setPoll(PrizePoll $poll) {
        $this->poll = $poll;
    }
}

// 使用範例
try {
    $prizePoll = new PrizePoll();
    $prizePoll->addItem(new Prize(1, 5, '一等獎'))
               ->addItem(new Prize(2, 6, '二等獎'))
               ->addItem(new Prize(3, 7, '三等獎'))
               ->addItem(new Prize(4, 2, '安慰獎'));

    $turn = new SimpleTurn($prizePoll);
    $prize = $turn->run();

    if ($prize) {
        echo "恭喜中獎:{$prize->name} (ID: {$prize->id}, 權重: {$prize->weight})";
    } else {
        echo "未中獎";
    }
} catch (Exception $e) {
    echo "錯誤:" . $e->getMessage();
}
?>

進階應用建議

  • 權重動態調整:可從資料庫讀取權重設定,支援後台即時修改機率
  • 防重複中獎:記錄用戶中獎紀錄,抽獎前過濾已中獎項目
  • 大數據場景:若獎品極多,可採用二分搜尋優化區間判斷效能
  • 分散式系統:搭配 Redis Lua 腳本實現原子性扣減庫存與抽獎

常見問題

Q: 為何使用 mt_rand 而非 rand?
A: mt_rand 基於 Mersenne Twister 演算法,速度更快且隨機品質更好,適合高併發場景。

Q: 權重為 0 的獎品會參與抽獎嗎?
A: 不會。getVisibleItems() 會自動過濾權重為 0 的獎品,確保其永不中獎。

這套架構清晰、擴展性強,可直接應用於電商促銷、遊戲掉寶、隨機推薦等業務場景。根據實際需求調整權重配置即可靈活控制中獎機率。

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