溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

使用PHP實(shí)現(xiàn)生成隨機(jī)紅包的算法

發(fā)布時(shí)間:2020-05-27 16:32:30 來(lái)源:億速云 閱讀:384 作者:PHP專家 欄目:編程語(yǔ)言

本篇文章和大家了解一下使用PHP實(shí)現(xiàn)生成隨機(jī)紅包的算法。有一定的參考價(jià)值,有需要的朋友可以參考一下,希望對(duì)大家有所幫助。

PHP是一種通用開(kāi)源腳本語(yǔ)言。語(yǔ)法吸收了C語(yǔ)言、Java和Perl的特點(diǎn),利于學(xué)習(xí),使用廣泛,主要適用于Web開(kāi)發(fā)領(lǐng)域。PHP 獨(dú)特的語(yǔ)法混合了C、Java、Perl以及PHP自創(chuàng)的語(yǔ)法。它可以比CGI或者Perl更快速地執(zhí)行動(dòng)態(tài)網(wǎng)頁(yè)。

基本思路

在隨機(jī)數(shù)生成方面,我借鑒了這位博主 @悲慘的大爺 的思路:

原文:比如要把 1 個(gè)紅包分給 N 個(gè)人,實(shí)際上就是相當(dāng)于要得到 N 個(gè)百分比數(shù)據(jù) 條件是這 N 個(gè)百分比之和 = 100/100。這 N 個(gè)百分比的平均值是 1/N。 并且這 N 個(gè)百分比數(shù)據(jù)符合一種正態(tài)分布(多數(shù)值比較靠近平均值)。

解讀:比如我有 1000 塊錢(qián),發(fā) 50 個(gè)紅包,就先隨機(jī)出 50 個(gè)數(shù),然后算出這 50 個(gè)數(shù)的均值 avg,用 avg/(1/N),就得到了一個(gè)基數(shù) mixrand ,然后用隨機(jī)出的那 50 個(gè)數(shù)分別去除以 mixrand ,得到每個(gè)數(shù)相對(duì)基數(shù)的百分比 randVal ,然后用 randVal 乘以 1000 塊錢(qián),就可以得到每個(gè)紅包的具體金額了。

算法實(shí)現(xiàn)

Talk is cheap, show me your code!

核心生成算法:

<?php
/*
 * Note: 紅包生成隨機(jī)算法
 */
class Reward
{
    public $rewardMoney;        // 紅包金額、單位元
    public $rewardNum;          // 紅包數(shù)量
    // 執(zhí)行紅包生成算法
    public function splitReward($rewardMoney, $rewardNum, $max, $min)
    {
        // 傳入紅包金額和數(shù)量,因?yàn)樾?shù)在計(jì)算過(guò)程中會(huì)出現(xiàn)很大誤差,所以我們直接把金額放大100倍,后面的計(jì)算全部用整數(shù)進(jìn)行
        $min = $min * 100;
        $max = $max * 100;
        // 預(yù)留出一部分錢(qián)作為誤差補(bǔ)償,保證每個(gè)紅包至少有一個(gè)最小值
        $this->rewardMoney = $rewardMoney * 100 - $rewardNum * $min;
        $this->rewardNum = $rewardNum;
        // 計(jì)算出發(fā)出紅包的平均概率值、精確到小數(shù)4位。
        $avgRand = 1 / $this->rewardNum;
        $randArr = [];
        // 定義生成的數(shù)據(jù)總合sum
        $sum = 0;
        $t_count = 0;
        while ($t_count < $rewardNum) {
            // 隨機(jī)產(chǎn)出四個(gè)區(qū)間的額度
            $c = rand(1, 100);
            if ($c < 15) {
                $t = round(sqrt(mt_rand(1, 1500)));
            } else if ($c < 65) {
                $t = round(sqrt(mt_rand(1500, 6500)));
            } else if ($c < 95) {
                $t = round(sqrt(mt_rand(6500, 9500)));
            } else {
                $t = round(sqrt(mt_rand(9500, 10000)));
            }
            ++$t_count;
            $sum += $t;
            $randArr[] = $t;
        }
        // 計(jì)算當(dāng)前生成的隨機(jī)數(shù)的平均值,保留4位小數(shù)
        $randAll = round($sum / $rewardNum, 4);
        // 為將生成的隨機(jī)數(shù)的平均值變成我們要的1/N,計(jì)算一下每個(gè)隨機(jī)數(shù)要除以的總基數(shù)mixrand。此處可以約等處理,產(chǎn)生的誤差后邊會(huì)找齊
        // 總基數(shù) = 均值/平均概率
        $mixrand = round($randAll / $avgRand, 4);
        // 對(duì)每一個(gè)隨機(jī)數(shù)進(jìn)行處理,并乘以總金額數(shù)來(lái)得出這個(gè)紅包的金額。
        $rewardArr = array();
        foreach ($randArr as $key => $randVal) {
            // 單個(gè)紅包所占比例randVal
            $randVal = round($randVal / $mixrand, 4);
            // 算出單個(gè)紅包金額
            $single = floor($this->rewardMoney * $randVal);
            // 小于最小值直接給最小值
            if ($single < $min) {
                $single += $min;
            }
            // 大于最大值直接給最大值
            if ($single > $max) {
                $single = $max;
            }
            // 將紅包放入結(jié)果數(shù)組
            $rewardArr[] = $single;
        }
        // 對(duì)比紅包總數(shù)的差異、將差值放在第一個(gè)紅包上
        $rewardAll = array_sum($rewardArr);
        // 此處應(yīng)使用真正的總金額rewardMoney,$rewardArr[0]可能小于0
        $rewardArr[0] = $rewardMoney * 100 - ($rewardAll - $rewardArr[0]);
        // 第一個(gè)紅包小于0時(shí),做修正
        if ($rewardArr[0] < 0) {
            rsort($rewardArr);
            $this->add($rewardArr, $min);
        }
        rsort($rewardArr);
        // 隨機(jī)生成的最大值大于指定最大值
        if ($rewardArr[0] > $max) {
            // 差額
            $diff = 0;
            foreach ($rewardArr as $k => &$v) {
                if ($v > $max) {
                    $diff += $v - $max;
                    $v = $max;
                } else {
                    break;
                }
            }
            $transfer = round($diff / ($this->rewardNum - $k + 1));
            $this->diff($diff, $rewardArr, $max, $min, $transfer, $k);
        }
        return $rewardArr;
    }
    // 處理所有超過(guò)最大值的紅包
    public function diff($diff, &$rewardArr, $max, $min, $transfer, $k)
    {
        // 將多余的錢(qián)均攤給小于最大值的紅包
        for ($i = $k; $i < $this->rewardNum; $i++) {
            // 造隨機(jī)值
            if ($transfer > $min * 20) {
                $aa = rand($min, $min * 20);
                if ($i % 2) {
                    $transfer += $aa;
                } else {
                    $transfer -= $aa;
                }
            }
            if ($rewardArr[$i] + $transfer > $max) continue;
            if ($diff - $transfer < 0) {
                $rewardArr[$i] += $diff;
                $diff = 0;
                break;
            }
            $rewardArr[$i] += $transfer;
            $diff -= $transfer;
        }
        if ($diff > 0) {
            $i++;
            $this->diff($diff, $rewardArr, $max, $min, $transfer, $k);
        }
    }
    // 第一個(gè)紅包小于0,從大紅包上往下減
    public function add(&$rewardArr, $min)
    {
        foreach ($rewardArr as &$re) {
            $dev = floor($re / $min);
            if ($dev > 2) {
                $transfer = $min * floor($dev / 2);
                $re -= $transfer;
                $rewardArr[$this->rewardNum - 1] += $transfer;
            } elseif ($dev == 2) {
                $re -= $min;
                $rewardArr[$this->rewardNum - 1] += $min;
            } else {
                break;
            }
        }
        if ($rewardArr[$this->rewardNum - 1] > $min || $rewardArr[$this->rewardNum - 1] == $min) {
            return;
        } else {
            $this->add($rewardArr, $min);
        }
    }
}

細(xì)節(jié)考慮

下邊這段代碼用來(lái)控制具體的業(yè)務(wù)邏輯,按照具體的需求,留出固定的最大值、最小值紅包的金額等;在代碼中調(diào)用生成紅包的方法時(shí) splitReward(total,num,max?0.01,min),我傳入的最大值減了 0.01,這樣就保證了里面生成的紅包最大值絕對(duì)不會(huì)超過(guò)我們?cè)O(shè)置的最大值。

<?php
class CreateReward{
    /*
     * 生成紅包
     * @param   int          $total               紅包總金額
     * @param   int          $num                 紅包總數(shù)量
     * @param   int          $max                 紅包最大值
     *
     */
    public function random_red($total, $num, $max, $min)
    {
        // 總共要發(fā)的紅包金額,留出一個(gè)最大值;
        $total = $total - $max;
        $reward = new Reward();
        $result_merge = $reward->splitReward($total, $num, $max - 0.01, $min);
        sort($result_merge);
        $result_merge[1] = $result_merge[1] + $result_merge[0];
        $result_merge[0] = $max * 100;
        foreach ($result_merge as &$v) {
            $v = floor($v) / 100;
        }
        return $result_merge;
    }
}

實(shí)例測(cè)試

基礎(chǔ)代碼

先設(shè)置好各種初始值。

<?php
/**
 * Created by PhpStorm.
 * User: lufei
 * Date: 2017/1/4
 * Time: 22:49
 */
header('content-type:text/html;charset=utf-8');
ini_set('memory_limit', '128M');
require_once('CreateReward.php');
require_once('Reward.php');
$total = 50000;
$num = 300000;
$max = 50;
$min = 0.01;
$create_reward = new CreateReward();

性能測(cè)試

因?yàn)?memory_limit 的限制,所以只測(cè)了 5 次的均值,結(jié)果都在 1.6s 左右。

for ($i=0; $i<5; $i++) {
    $time_start = microtime_float();
    $reward_arr = $create_reward->random_red($total, $num, $max, $min);
    $time_end = microtime_float();
    $time[] = $time_end - $time_start;
}
echo array_sum($time)/5;
function microtime_float()
{
    list($usec, $sec) = explode(" ", microtime());
    return ((float)$usec + (float)$sec);
}

運(yùn)行結(jié)果:

數(shù)據(jù)檢查

1) 數(shù)值是否有誤

檢測(cè)有沒(méi)有負(fù)值,有沒(méi)有最大值,最大值有多少個(gè),有沒(méi)有小于最小值的值。

$reward_arr = $create_reward->random_red($total, $num, $max, $min);
sort($reward_arr);//正序,最小的在前面
$sum = 0;
$min_count = 0;
$max_count = 0;
foreach($reward_arr as $i => $val) {
    if ($i<3) {
        echo "<br />第".($i+1)."個(gè)紅包,金額為:".$val."<br />";
    }
    if ($val == $max) {
          $max_count++;
    }
    if ($val < $min) {
        $min_count++;
    }
    $val = $val*100;
    $sum += $val;
}
//檢測(cè)錢(qián)是否全部發(fā)完
echo '<hr>已生成紅包總金額為:'.($sum/100).';總個(gè)數(shù)為:'.count($reward_arr).'<hr>';
//檢測(cè)有沒(méi)有小于0的值
echo "<br />最大值:".($val/100).',共有'.$max_count.'個(gè)最大值,共有'.$min_count.'個(gè)值比最小值小';

運(yùn)行結(jié)果:

2) 正態(tài)分布情況

注意,出圖的時(shí)候,紅包的數(shù)量不要給的太大,不然頁(yè)面渲染不出來(lái),會(huì)崩 。

$reward_arr = $create_reward->random_red($total, $num, $max, $min);
$show = array();
rsort($reward_arr);
// 為了更直觀的顯示正態(tài)分布效果,需要將數(shù)組重新排序
foreach($reward_arr as $k=>$value)
{
    $t=$k%2;
    if(!$t) $show[]=$value;;
    else array_unshift($show,$value);
}
echo "設(shè)定最大值為:".$max.',最小值為:'.$min.'<hr />';
echo "<table style='font-size:12px;width:600px;border:1px solid #ccc;text-align:left;'><tr><td>紅包金額</td><td>圖示</td></tr>";
foreach($show as $val)
{
    // 線條長(zhǎng)度計(jì)算
    $width=intval($num*$val*300/$total);
    echo "<tr><td> {$val} </td><td width='500px;text-align:left;'><hr style='width:{$width}px;height:3px;border:none;border-top:3px double red;margin:0 auto 0 0px;'></td></tr>";
}
echo "</table>";

運(yùn)行結(jié)果:

PS:有朋友問(wèn)我生成的數(shù)據(jù)有沒(méi)有通過(guò)數(shù)學(xué)方法來(lái)驗(yàn)證其是否符合標(biāo)準(zhǔn)正態(tài)分布,因?yàn)槲业臄?shù)學(xué)不好,這個(gè)還真沒(méi)算過(guò),只是看著覺(jué)得像,就當(dāng)他是了。既然遇到了這個(gè)問(wèn)題,就一定要解決嘛,所以我就用 php 內(nèi)置函數(shù)算了一下,算出來(lái)的結(jié)果在數(shù)據(jù)量小的時(shí)候還是比較接近正態(tài)分布的,但是數(shù)據(jù)量大起來(lái)的時(shí)候就不能看了,我整不太明白這個(gè),大家感興趣的可以找一下原因喲。

php 的四個(gè)函數(shù):stats_standard_deviation(標(biāo)準(zhǔn)差),stats_variance(方差), stats_kurtosis((峰度),stats_skew(偏度)。使用上面的函數(shù)需要安裝 stats 擴(kuò)展。

以上就是使用PHP實(shí)現(xiàn)生成隨機(jī)紅包的算法的詳細(xì)內(nèi)容,更多請(qǐng)關(guān)注億速云其它相關(guān)文章!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

php
AI