Maison  >  Article  >  développement back-end  >  Algorithme PHP pour générer des enveloppes rouges aléatoires

Algorithme PHP pour générer des enveloppes rouges aléatoires

PHPz
PHPzoriginal
2017-04-04 14:31:352390parcourir

1. Introduction au contexte

Il y a quelque temps, l'activité de l'entreprise avait besoin de générer des enveloppes rouges, qui sont divisées en enveloppes rouges fixes et enveloppes rouges aléatoires. Il n'y a rien à dire sur les enveloppes rouges fixes. les enveloppes rouges et les enveloppes rouges aléatoires nécessitent une valeur minimale, et la valeur maximale, il doit y avoir au moins une valeur maximale, il ne peut y avoir de valeur minimale, mais toute enveloppe rouge ne peut pas être inférieure à la valeur minimale.
Je n'avais jamais fait cela auparavant et j'étais un peu confus, alors je suis allé sur Baidu et j'ai découvert que tous les algorithmes d'enveloppe rouge que j'ai pu trouver avaient divers bugs. Ils calculaient des valeurs négatives ou dépassaient la valeur maximale, donc. J'ai décidé de faire un ensemble moi-même.

Algorithme PHP pour générer des enveloppes rouges aléatoires


2. Idées de base

En termes de génération de nombres aléatoires, j'ai appris de ceci Les pensées d'un blogueur@misérable oncle :

Texte original : Par exemple, si vous souhaitez distribuer 1 enveloppe rouge à N personnes, cela équivaut en fait à recevoir N données en pourcentage La condition est que la somme de ces N pourcentages = 100/100. La moyenne de ces N pourcentages est de 1/N. Et ces N données en pourcentage sont conformes à une distribution normale (la plupart des valeurs sont plus proches de la moyenne).
Interprétation : Par exemple, si j'ai 1 000 yuans et que je distribue 50 enveloppes rouges, je choisirai d'abord au hasard 50 nombres, puis je calculerai la moyenne $avg de ces 50 nombres, en utilisant $avg /(1/N ), vous obtenez un $mixrand de base, puis divisez les 50 nombres générés aléatoirement par $mixrand pour obtenir le pourcentage de chaque nombre par rapport à la base $randVal, puis multipliez $randVal par 1 000 yuans pour obtenir chacun Le montant précis de l’enveloppe rouge.

Vous ne savez toujours pas ce qui se passe ? Ce n’est pas grave, codons ensemble !

Algorithme PHP pour générer des enveloppes rouges aléatoires


3. Talk n'est pas cher, montre-moi ton code !

Génération enveloppe rouge Algorithme de base :
<?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);
        }
    }
}
Détails à prendre en compte :

Le code ci-dessous est utilisé pour contrôler une logique métier spécifique et mettre de côté des montants fixes maximum et minimum d'enveloppe rouge en fonction de besoins spécifiques ;
Lors de l'appel de splitReward($total, $num,$max - 0.01, $min); dans le code, la valeur maximale que j'ai transmise a été réduite de 0,01, garantissant ainsi que l'enveloppe rouge générée à l'intérieur est la plus grande. ne dépassez jamais la valeur maximale que nous avons fixée.

<?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. Sortez-le pour une promenade

Code de base :

Définissez diverses valeurs initiales

<?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();
Test de performance :

En raison de la limitation de memory_limit, je n'ai mesuré la valeur moyenne que 5 fois et les résultats étaient tous autour de 1,6 s.

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

Résultats d'exécution :

Algorithme PHP pour générer des enveloppes rouges aléatoires


Vérification des données :

Détecter s'il y a des valeurs négatives, s'il y a une valeur maximale, combien de valeurs maximales il y a et s'il y a une valeur inférieure à la valeur minimale

$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.'个值比最小值小';

Exécuter les résultats ; :

Algorithme PHP pour générer des enveloppes rouges aléatoires


Tableau de distribution normale :

Notez que lorsque vous dessinez l'image , ne donnez pas trop d'enveloppes rouges, sinon la page ne pourra pas être rendue et plantera

Algorithme PHP pour générer des enveloppes rouges aléatoires


$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>";
.

Résultat d'exécution :

Algorithme PHP pour générer des enveloppes rouges aléatoires


PS : Un ami m'a demandé si les données générées avaient a été mathématiquement vérifié pour se conformer à la normalité standard, parce que je ne suis pas bon en mathématiques, je ne l'ai jamais calculé, je l'ai juste regardé et j'ai pensé que cela y ressemblait, alors je l'ai pris tel quel.
Depuis que j'ai rencontré ce problème, je dois le résoudre, j'ai donc utilisé la fonction intégrée PHP pour faire le calcul. Le résultat calculé est encore relativement proche de la distribution normale lorsque la quantité de données est importante. est petit. Mais lorsque la quantité de données augmente, elle ne peut pas être visualisée. Si cela vous intéresse, vous pouvez en découvrir la raison.
Quatre fonctions de PHP : stats_standard_deviation (écart type), stats_variance (variance), stats_kurtosis (kurtosis), stats_skew (asymétrie)
L'utilisation des fonctions ci-dessus nécessite l'installation de l'extension de statistiques@Adresse de téléchargement

5. Au final

À ce stade, l'enveloppe rouge a été écrite. Je ne sais pas si je peux obtenir une augmentation de salaire de 50 yuans, mais cela devrait pouvoir résoudre le problème. besoin urgent.

Algorithme PHP pour générer des enveloppes rouges aléatoires



Oh oui, j'ai aussi laissé ce téléchargement de package de code

Algorithme PHP pour générer des enveloppes rouges aléatoires



Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn