얼마 전 회사에서 빨간 봉투를 생성해야 하는 상황이 있었는데, 고정 빨간 봉투와 임의 빨간 봉투로 나뉘는데요. 빨간색 봉투 및 임의 빨간색 봉투에는 최소값이 필요하며 최대값에는 최대값이 하나 이상 있어야 하며 최소값은 없지만 빨간색 봉투는 최소값보다 작을 수 없습니다.
한번도 해본 적이 없어서 조금 헷갈려서 바이두에 가보니 제가 찾아본 레드엔벨로프 알고리즘은 전부 음수값을 계산하거나 최대값을 초과하는 버그가 많았습니다. 직접 세트를 만들기로 결정했습니다.
난수 생성에 관해서는 다음에서 배웠습니다. this 블로거@비참한 아저씨의 생각:
원문: 예를 들어 N명에게 빨간 봉투 1개를 나눠준다면 실제로 N을 얻는 것과 같습니다. 백분율 데이터 조건은 이 N 백분율의 합 = 100/100입니다. 이러한 N 백분율의 평균은 1/N입니다. 그리고 이 N% 데이터는 정규 분포를 따릅니다(대부분의 값이 평균에 더 가깝습니다).
해석: 예를 들어 1,000위안이 있고 빨간 봉투 50개를 나눠준다면 먼저 무작위로 50개의 숫자를 선택한 다음 $avg를 사용하여 이 50개 숫자의 평균 $avg를 계산합니다. /(1/N ), 기본 $mixrand를 얻은 다음 무작위로 생성된 50개의 숫자를 $mixrand로 나누어 기본 $randVal에 대한 각 숫자의 비율을 구한 다음 $randVal에 1,000위안을 곱하여 각각을 얻습니다. 빨간 봉투의 특정 금액입니다.
아직도 무슨 일이 일어나고 있는지 모르시나요? 상관없습니다. 함께 코딩해 봅시다!
<?php /* * Author:xx_lufei * Time:2016年9月14日09:55:36 * 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 = array(); #定义生成的数据总合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); $rewardArr[0] = $rewardMoney * 100 - ($rewardAll - $rewardArr[0]);#此处应使用真正的总金额rewardMoney,$rewardArr[0]可能小于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{ /* * 生成红包 * author xx 2016年9月23日13:53:38 * @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.6초 정도였습니다.
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); }
실행 결과:
음수 값이 있는지, 최대값이 있는지, 최대값이 몇 개 있는지, 최소값보다 작은 값이 있는지 감지
$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.'个值比最小值小';
결과 실행:
그림을 그릴 때 빨간 봉투를 너무 많이 주면 페이지가 렌더링됩니다. 안 나오면 페이지가 접혀요
$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 중국어 웹사이트의 기타 관련 기사를 참조하세요!