PHP 生成随机红包算法
根本思绪
在随机数生成方面,我借鉴了这位博主 @悲凉的大爷 的思绪:
原文:比方要把 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 生成随机红包算法的具体内容,更多请关注百分百源码网其它相关文章!