PHP 生成随机红包算法-php教程

资源魔 31 0

根本思绪

正在随机数天生方面,我自创了这位博主 @凄惨的年夜爷 的思绪:

原文:比方要把 1 个红包分给 N 集体,实际上就是相称于要失去 N 个百分比数据 前提是这 N 个百分比之以及 = 100/100。这 N 个百分比的均匀值是 1/N。 而且这 N 个百分比数据合乎一种正态散布(少数值比拟接近均匀值)。

解读:比方我有 1000 块钱,发 50 个红包,就先随机出 50 个数,而后算出这 50 个数的均值 avg,用 avg/(1/N),就失去了一个基数 mixrand ,而后用随机出的那 50 个数辨别去除了以 mixrand ,失去每一个数绝对基数的百分比 randVal ,而后用 randVal 乘以 1000 块钱,就能够失去每一个红包的详细金额了。

算法完成

Talk is cheap, show me your code!

外围天生算法:

<?php
/*
 * Note: 红包天生随机算法
 */
class Reward
{
    public $rewardMoney;        // 红包金额、单元元
    public $rewardNum;          // 红包数目
    // 执行红包天生算法
    public function splitReward($rewardMoney, $rewardNum, $max, $min)
    {
        // 传入红包金额以及数目,由于小数正在较量争论进程中会呈现很年夜偏差,以是咱们间接把金额放年夜100倍,前面的较量争论全副用整数进行
        $min = $min * 100;
        $max = $max * 100;
        // 预留出一局部钱作为偏差弥补,保障每一个红包至多有一个最小值
        $this->rewardMoney = $rewardMoney * 100 - $rewardNum * $min;
        $this->rewardNum = $rewardNum;
        // 较量争论登程出红包的均匀几率值、准确到小数4位。
        $avgRand = 1 / $this->rewardNum;
        $randArr = [];
        // 界说天生的数据总合sum
        $sum = 0;
        $t_count = 0;
        while ($t_count < $rewardNum) {
            // 随机产出四个区间的额度
            $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;
        }
        // 较量争论以后天生的随机数的均匀值,保存4位小数
        $randAll = round($sum / $rewardNum, 4);
        // 为将天生的随机数的均匀值变为咱们要的1/N,较量争论一下每一个随机数要除了以的总基数mixrand。此处能够约等解决,孕育发生的偏差后边会找齐
        // 总基数 = 均值/均匀几率
        $mixrand = round($randAll / $avgRand, 4);
        // 对每个随机数进行解决,并乘以总金额数来患上出这个红包的金额。
        $rewardArr = array();
        foreach ($randArr as $key => $randVal) {
            // 单个红包所占比例randVal
            $randVal = round($randVal / $mixrand, 4);
            // 算出单个红包金额
            $single = floor($this->rewardMoney * $randVal);
            // 小于最小值间接给最小值
            if ($single < $min) {
                $single += $min;
            }
            // 年夜于最年夜值间接给最年夜值
            if ($single > $max) {
                $single = $max;
            }
            // 将红包放入后果数组
            $rewardArr[] = $single;
        }
        // 比照红包总数的差别、将差值放正在第一个红包上
        $rewardAll = array_sum($rewardArr);
        // 此处应应用真实的总金额rewardMoney,$rewardArr[0]可能小于0
        $rewardArr[0] = $rewardMoney * 100 - ($rewardAll - $rewardArr[0]);
        // 第一个红包小于0时,做修改
        if ($rewardArr[0] < 0) {
            rsort($rewardArr);
            $this->add($rewardArr, $min);
        }
        rsort($rewardArr);
        // 随机天生的最年夜值年夜于指定最年夜值
        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;
    }
    // 解决一切超越最年夜值的红包
    public function diff($diff, &$rewardArr, $max, $min, $transfer, $k)
    {
        // 将过剩的钱均派给小于最年夜值的红包
        for ($i = $k; $i < $this->rewardNum; $i++) {
            // 造随机值
            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);
        }
    }
    // 第一个红包小于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);
        }
    }
}

细节思考

下边这段代码用来管制详细的营业逻辑,依照详细的需要,留出固定的最年夜值、最小值红包的金额等;正在代码中挪用天生红包的办法时 splitReward(total,num,max−0.01,min),我传入的最年夜值减了 0.01,这样就保障了外面天生的红包最年夜值相对没有会超越咱们设置的最年夜值。

<?php
class CreateReward{
    /*
     * 天生红包
     * @param   int          $total               红包总金额
     * @param   int          $num                 红包总数目
     * @param   int          $max                 红包最年夜值
     *
     */
    public function random_red($total, $num, $max, $min)
    {
        // 统共要发的红包金额,留出一个最年夜值;
        $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;
    }
}

实例测试

根底代码

先设置好各类初始值。

<?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();

功能测试

由于 memory_limit 的限度,以是只测了 5 次的均值,后果都正在 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);
}

运转后果:

数据反省

1) 数值能否有误

检测有无负值,有无最年夜值,最年夜值有几何个,有无小于最小值的值。

$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)."个红包,金额为:".$val."<br />";
    }
    if ($val == $max) {
          $max_count++;
    }
    if ($val < $min) {
        $min_count++;
    }
    $val = $val*100;
    $sum += $val;
}
//检测钱能否全副发完
echo '<hr>已天生红包总金额为:'.($sum/100).';总个数为:'.count($reward_arr).'<hr>';
//检测有无小于0的值
echo "<br />最年夜值:".($val/100).',共有'.$max_count.'个最年夜值,共有'.$min_count.'个值比最小值小';

运转后果:

2) 正态散布状况

留意,出图的时分,红包的数目没有要给的太年夜,否则页面衬着没有进去,会崩 。

$reward_arr = $create_reward->random_red($total, $num, $max, $min);
$show = array();
rsort($reward_arr);
// 为了更直观的显示正态散布成果,需求将数组从新排序
foreach($reward_arr as $k=>$value)
{
    $t=$k%2;
    if(!$t) $show[]=$value;;
    else array_unshift($show,$value);
}
echo "设定最年夜值为:".$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)
{
    // 线条长度较量争论
    $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>";

运转后果:

PS:有冤家问我天生的数占有不经过数学办法来验证其能否合乎规范正态散布,由于我的数学欠好,这个还真没算过,只是看着感觉像,就当他是了。既然遇到了这个成绩,就肯定要处理嘛,以是我就用 php 内置函数算了一下,算进去的后果正在数据量小的时分仍是比拟靠近正态散布的,然而数据量年夜起来的时分就不克不及看了,我整没有太明确这个,各人感兴味的能够找一下缘由哟。

php 的四个函数:stats_standard_deviation(规范差),stats_variance(方差), stats_kurtosis((峰度),stats_skew(偏偏度)。应用下面的函数需求装置 stats 扩大。

以上就是PHP 天生随机红包算法的具体内容,更多请存眷资源魔其它相干文章!

标签: php php开发教程 php开发资料 php开发自学

抱歉,评论功能暂时关闭!