>  기사  >  백엔드 개발  >  임의의 빨간색 봉투를 생성하는 PHP 알고리즘

임의의 빨간색 봉투를 생성하는 PHP 알고리즘

PHPz
PHPz원래의
2017-04-04 14:31:352301검색

1. 배경 소개

얼마 전 회사에서 빨간 봉투를 생성해야 하는 상황이 있었는데, 고정 빨간 봉투와 임의 빨간 봉투로 나뉘는데요. 빨간색 봉투 및 임의 빨간색 봉투에는 최소값이 필요하며 최대값에는 최대값이 하나 이상 있어야 하며 최소값은 없지만 빨간색 봉투는 최소값보다 작을 수 없습니다.
한번도 해본 적이 없어서 조금 헷갈려서 바이두에 가보니 제가 찾아본 레드엔벨로프 알고리즘은 전부 음수값을 계산하거나 최대값을 초과하는 버그가 많았습니다. 직접 세트를 만들기로 결정했습니다.

임의의 빨간색 봉투를 생성하는 PHP 알고리즘


2. 기본 아이디어

난수 생성에 관해서는 다음에서 배웠습니다. 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 알고리즘


3. 톡은 싸다, 코드 보여줘!

빨간 봉투 세대 핵심 알고리즘:
<?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;
    }
}

4. 뽑아서 산책

기본코드:

다양한 초기값 설정

<?php
/**
 * Created by PhpStorm.
 * User: lufei
 * Date: 2017/1/4
 * Time: 22:49
 */
header(&#39;content-type:text/html;charset=utf-8&#39;);
ini_set(&#39;memory_limit&#39;, &#39;128M&#39;);

require_once(&#39;CreateReward.php&#39;);
require_once(&#39;Reward.php&#39;);

$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);
}

실행 결과:

임의의 빨간색 봉투를 생성하는 PHP 알고리즘


데이터 확인:

음수 값이 있는지, 최대값이 있는지, 최대값이 몇 개 있는지, 최소값보다 작은 값이 있는지 감지

$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 &#39;<hr>已生成红包总金额为:'.($sum/100).';总个数为:'.count($reward_arr).'<hr>';
//检测有没有小于0的值
echo "<br />最大值:".($val/100).',共有'.$max_count.'个最大值,共有'.$min_count.'个值比最小值小';

결과 실행:

임의의 빨간색 봉투를 생성하는 PHP 알고리즘


정규 분포도:

그림을 그릴 때 빨간 봉투를 너무 많이 주면 페이지가 렌더링됩니다. 안 나오면 페이지가 접혀요

임의의 빨간색 봉투를 생성하는 PHP 알고리즘


$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=&#39;font-size:12px;width:600px;border:1px solid #ccc;text-align:left;&#39;><tr><td>红包金额</td><td>图示</td></tr>";
foreach($show as $val)
{
    #线条长度计算
    $width=intval($num*$val*300/$total);
    echo "<tr><td> {$val} </td><td width=&#39;500px;text-align:left;&#39;><hr style=&#39;width:{$width}px;height:3px;border:none;border-top:3px double red;margin:0 auto 0 0px;&#39;></td></tr>";
}
echo "</table>";

실행 결과:

임의의 빨간색 봉투를 생성하는 PHP 알고리즘


PS: 친구가 생성된 데이터에 표준 정규 분포를 따르는 것으로 수학적으로 검증되었습니다. 왜냐하면 내 수학이 아니기 때문입니다. 저는 이것에 대해 정말로 생각해 본 적이 없으며 단지 그 사람처럼 보였고 그 사람일 것이라고 생각했습니다.
이 문제가 발생했기 때문에 이를 해결해야 하므로 PHP 내장 함수 를 사용하여 계산을 수행했습니다. 계산된 결과는 데이터 양이 많을 때 여전히 정규 분포에 가깝습니다. 그런데 데이터의 양이 늘어나면 볼 수 없게 됩니다. 관심을 가지시면 그 이유를 알 수 있습니다.
PHP의 네 가지 기능: stats_standard_deviation(표준편차), stats_variance(분산), stats_kurtosis(첨도), stats_skew(편차)
위 기능을 사용하려면 stats 확장 프로그램@다운로드 주소

5. 결국

이 시점에서 빨간 봉투에 급여를 50위안 올릴 수 있을지는 모르겠지만 해결은 해야 할 것 같습니다. 긴급한 필요.


임의의 빨간색 봉투를 생성하는 PHP 알고리즘


아 네, 이 코드 패키지 다운로드도 남겼습니다

임의의 빨간색 봉투를 생성하는 PHP 알고리즘



위 내용은 임의의 빨간색 봉투를 생성하는 PHP 알고리즘의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.