1. 程式人生 > >隨機生成紅包演算法

隨機生成紅包演算法

<?php
/**
 * Created by PhpStorm.
 * User: kevin_yang
 * Date: 2018/1/4
 * Time: 22:49
 */
header('content-type:text/html;charset=utf-8');
ini_set('memory_limit', '256M');

require_once('CreateReward.php');
require_once('Reward.php');

$total = 100;
$num = 10;
$max = mt_rand(1,2)*(ceil($total/$num));
$min = 0.1;

$create_reward = new CreateReward(); //效能測試 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 "<pre />";var_dump($reward_arr); //檢測錢是否全部發完 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 "<pre>";var_dump($show);die; 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>&nbsp;{$val}&nbsp;</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>";

 


引入檔案Reward.php

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

引入檔案CreateReward.php

<?php 
class CreateReward{
	/*
     * 生成紅包
     * author    xx     2018年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;
    }
}

  執行結果

第1個紅包,金額為:6.67

第2個紅包,金額為:7.73

第3個紅包,金額為:7.83

array(10) {
  [0]=>
  float(6.67)
  [1]=>
  float(7.73)
  [2]=>
  float(7.83)
  [3]=>
  float(8.46)
  [4]=>
  float(8.89)
  [5]=>
  float(9.21)
  [6]=>
  float(10.15)
  [7]=>
  float(10.48)
  [8]=>
  float(10.58)
  [9]=>
  float(20)
}

已生成紅包總金額為:100;總個數為:10