微信隨機(jī)紅包數(shù)詳解和算法代碼

1 需求

寫一個(gè)固定紅包 + 隨機(jī)紅包

  1. 固定紅包就是每個(gè)紅包金額一樣,有多少個(gè)就發(fā)多少個(gè)固定紅包金額就行。
  2. 隨機(jī)紅包的需求是。比如紅包總金額5元,需要發(fā)10個(gè)紅包。隨機(jī)范圍是 0.01到0.99;5元必需發(fā)完,金額需要有一定趨勢(shì)的正態(tài)分布。(0.99可以任意指定,也可以是 avg * 2 - 0.01;比如avg = 5 / 10 = 0.5;(avg * 2 - 0.01 = 0.99))

2 需求分析

2.1 固定紅包

如果是固定紅包,則算法是一條直線。t就是固定紅包的額度。如圖。
f(x) = t;(1 <= x <= num)

image.png

2.2 隨機(jī)紅包

如果我們使用隨機(jī)函數(shù)rand。rand(0.01,0.99);那么10次隨機(jī),如果最壞情況都是金額0.99,總金額就是9.9元。會(huì)超過5元。金額也會(huì)不正態(tài)分布。最后思考了一下借助與數(shù)學(xué)函數(shù)來當(dāng)作隨機(jī)紅包的發(fā)生器,可以用拋物線,三角函數(shù)。最后選定了等腰三角線性函數(shù)。

1 算法原理

如果需要發(fā)紅包總金額是totalMoney,紅包個(gè)數(shù)是num個(gè),金額范圍是[min,max],線性方程如圖。

image.png

三個(gè)點(diǎn)的坐標(biāo):
(x1,y1) = (1,min)
(x2,y2) = (num/2,max)
(x3,y3) = (num,min)

確定的線性方程:
$y = 1.0 * ($x - $x1) / ($x2 - $x1) * ($y2 - $y1) + $y1 ; (x1 <= x <= x2)
$y = 1.0 * ($x - $x2) / ($x3 - $x2) * ($y3 - $y2) + $y2; (x2 <= x <= x3)

修數(shù)據(jù):
y(合) = y1 + y2 + y3 +...... ynum;
y(合)有可能 > totalMoney ,說明生成金額多了,需要修數(shù)據(jù),則從(y1,y2,y3.....ynum)這些每次減少0.01。直到y(tǒng)(合) = totalMoney。
y(合)有可能 < totalMoney ,說明生成金額少了,需要修數(shù)據(jù),則從(y1,y2,y3.....ynum)這些每次加上0.01。直到y(tǒng)(合) = totalMoney。

2 算法原理樣例

如果需要發(fā)紅包總金額是11470,紅包個(gè)數(shù)是7400個(gè),金額范圍是[0.01,3.09],線性方程如圖。

image.png

3 需求設(shè)計(jì)

3.1 類圖設(shè)計(jì)

image.png

3.2 源碼設(shè)計(jì)

<?php  
/** 
 * 隨機(jī)紅包+固定紅包算法[策略模式] 
 * copyright (c) 2016 http://blog.csdn.net/CleverCode 
 */  
//配置傳輸數(shù)據(jù)DTO  
class OptionDTO  
{
    //紅包總金額  
    public $totalMoney;  
    //紅包數(shù)量  
    public $num;  
    //范圍開始  
    public $rangeStart;  
    //范圍結(jié)算  
    public $rangeEnd;  
    //生成紅包策略  
    public $builderStrategy;  
    //隨機(jī)紅包剩余規(guī)則  
    public $randFormatType; //Can_Left:不修數(shù)據(jù),可以有剩余;No_Left:不能有剩余  
    public static function create($totalMoney,$num,$rangeStart,$rangEnd,  
        $builderStrategy,$randFormatType = 'No_Left')  
    {
        $self = new self();  
        $self->num = $num;  
        $self->rangeStart = $rangeStart;  
        $self->rangeEnd = $rangEnd;  
        $self->totalMoney = $totalMoney;  
        $self->builderStrategy = $builderStrategy;  
        $self->randFormatType = $randFormatType;  
        return $self;   
    }
}
  
//紅包生成器接口  
interface IBuilderStrategy  
{
    //創(chuàng)建紅包  
    public function create();      
    //設(shè)置配置  
    public function setOption(OptionDTO $option);   
    //是否可以生成紅包  
    public function isCanBuilder();  
    //生成紅包函數(shù)  
    public function fx($x);  
}
  
//固定等額紅包策略  
class EqualPackageStrategy implements IBuilderStrategy  
{
    //單個(gè)紅包金額  
    public $oneMoney;  
    //數(shù)量  
    public $num;  
    public function __construct($option = null)   
    {  
        if($option instanceof OptionDTO)  
        {  
            $this->setOption($option);  
        }  
    }  
  
    public function setOption(OptionDTO $option)  
    {  
        $this->oneMoney = $option->rangeStart;  
        $this->num = $option->num;  
    }  
  
    public function create()   
    {
        $data = array();  
        if(false == $this->isCanBuilder())  
        {  
            return $data;      
        }  
        $data = array();  
        if(false == is_int($this->num) || $this->num <= 0)   
        {  
            return $data;      
        }  
        for($i = 1;$i <= $this->num;$i++)  
        {  
            $data[$i] = $this->fx($i);  
        }  
        return $data;  
    }
      
    /** 
     * 等額紅包的方程是一條直線  
     *  
     * @param mixed $x  
     * @access public 
     * @return void 
     */  
    public function fx($x)   
    {
        return $this->oneMoney;   
    }
  
    /** 
     * 是否能固定紅包  
     *  
     * @access public 
     * @return void 
     */  
    public function isCanBuilder()  
    {
        if(false == is_int($this->num) || $this->num <= 0)   
        {  
            return false;      
        }  
  
        if(false ==  is_numeric($this->oneMoney) || $this->oneMoney <= 0)  
        {  
            return false;  
        }  
  
        //單個(gè)紅包小于1分  
        if($this->oneMoney < 0.01)  
        {  
            return false;  
        }  
          
        return true;  
    }
}
  
//隨機(jī)紅包策略(三角形)  
class RandTrianglePackageStrategy implements IBuilderStrategy  
{
    //總額  
    public $totalMoney;  
    //紅包數(shù)量  
    public $num;  
    //隨機(jī)紅包最小值  
    public $minMoney;  
    //隨機(jī)紅包最大值  
    public $maxMoney;  
    //修數(shù)據(jù)方式:NO_LEFT: 紅包總額 = 預(yù)算總額;CAN_LEFT: 紅包總額 <= 預(yù)算總額  
    public $formatType;   
    //預(yù)算剩余金額  
    public $leftMoney;  
  
  public function __construct($option = null)   
    {
        if($option instanceof OptionDTO)  
        {  
            $this->setOption($option);  
        }  
    }
  
    public function setOption(OptionDTO $option)  
    {
        $this->totalMoney = $option->totalMoney;  
        $this->num = $option->num;  
        $this->formatType = $option->randFormatType;  
        $this->minMoney = $option->rangeStart;  
        $this->maxMoney = $option->rangeEnd;  
        $this->leftMoney = $this->totalMoney;  
    }
  
    /** 
     * 創(chuàng)建隨機(jī)紅包  
     *  
     * @access public 
     * @return void 
     */  
    public function create()   
    {
        $data = array();  
        if(false == $this->isCanBuilder())  
        {  
            return $data;      
        }  
          
        $leftMoney = $this->leftMoney;  
        for($i = 1;$i <= $this->num;$i++)  
        {  
            $data[$i] = $this->fx($i);  
            $leftMoney = $leftMoney - $data[$i];   
        }  
  
        //修數(shù)據(jù)  
        list($okLeftMoney,$okData) = $this->format($leftMoney,$data);  
  
        //隨機(jī)排序  
        shuffle($okData);  
        $this->leftMoney = $okLeftMoney;  
  
        return $okData;  
    }
  
    /** 
     * 是否能夠發(fā)隨機(jī)紅包  
     * @access public 
     * @return void 
     */  
    public function isCanBuilder()  
    {
        if(false == is_int($this->num) || $this->num <= 0)   
        {  
            return false;      
        }  
  
        if(false ==  is_numeric($this->totalMoney) || $this->totalMoney <= 0)  
        {  
            return false;  
        }  
  
        //均值  
        $avgMoney = $this->totalMoney / 1.0 / $this->num;  
          
        //均值小于最小值  
        if($avgMoney < $this->minMoney )  
        {  
            return false;  
        }  
          
        return true;  
    }
  
    /** 
     * 獲取剩余金額  
     * @access public 
     * @return void 
     */  
    public function getLeftMoney()  
    {
        return $this->leftMoney;  
    }
  
    /** 
     * 隨機(jī)紅包生成函數(shù)。三角函數(shù)。[(1,0.01),($num/2,$avgMoney),($num,0.01)]  
     * @param mixed $x,1 <= $x <= $this->num;  
     * @access public 
     * @return void 
     */  
    public function fx($x)  
    {
        if(false == $this->isCanBuilder())  
        {  
            return 0;  
        }  
        if($x < 1 || $x > $this->num)  
        {  
            return 0;  
        }  
          
        $x1 = 1;  
        $y1 = $this->minMoney;  
        
        //中間點(diǎn)  
        $x2 = ceil($this->num /  1.0 / 2);  
        //我的峰值  
        $y2 = $this->maxMoney;  
  
        //最后點(diǎn)  
        $x3 = $this->num;  
        $y3 = $this->minMoney;    
  
        //當(dāng)x1,x2,x3都是1的時(shí)候(豎線)  
        if($x1 == $x2 && $x2 == $x3)  
        {  
            return $y2;  
        }  
  
        // '/_\'三角形狀的線性方程  
        //'/'部分  
        if($x1 != $x2 && $x >= $x1 && $x <= $x2)  
        {  
  、      $y = 1.0 * ($x - $x1) / ($x2 - $x1) * ($y2 - $y1) + $y1;    
            return number_format($y, 2, '.', '');  
        }  
  
        //'\'形狀  
        if($x2 != $x3 && $x >= $x2 && $x <= $x3)  
        {  
            $y = 1.0 * ($x - $x2) / ($x3 - $x2) * ($y3 - $y2) + $y2;    
            return number_format($y, 2, '.', '');  
        }  
          
        return 0;  
    }
  
    /** 
     * 格式化修紅包數(shù)據(jù)  
     *  
     * @param mixed $leftMoney  
     * @param array $data  
     * @access public 
     * @return void 
     */  
    private function format($leftMoney,array $data)  
    {
        //不能發(fā)隨機(jī)紅包  
        if(false == $this->isCanBuilder())  
        {  
            return array($leftMoney,$data);    
        }  
          
        //紅包剩余是0  
        if(0 == $leftMoney)  //無需修數(shù)據(jù)
        {  
            return array($leftMoney,$data);    
        }  
  
        //數(shù)組為空  
        if(count($data) < 1)  
        {  
            return array($leftMoney,$data);    
        }  
  
        //如果是可以有剩余,并且$leftMoney > 0  
        if('Can_Left' == $this->formatType  
          && $leftMoney > 0)  
        {  
            return array($leftMoney,$data);    
        }  

        // 如果還有余錢,則嘗試加到小紅包里,如果加不進(jìn)去,則嘗試下一個(gè)。  
        while($leftMoney > 0)  
        {  
            $found = 0;  
            foreach($data as $key => $val)   
            {  
                //減少循環(huán)優(yōu)化  
                if($leftMoney <= 0)  
                {  
                    break;  
                }  
  
                //預(yù)判  
                $afterLeftMoney =  (double)$leftMoney - 0.01;  
                $afterVal = (double)$val + 0.01;  
                if( $afterLeftMoney >= 0  && $afterVal <= $this->maxMoney)  
                {  
                    $found = 1;  
                    $data[$key] = number_format($afterVal,2,'.','');  
                    $leftMoney = $afterLeftMoney;  
                    //精度  
                    $leftMoney = number_format($leftMoney,2,'.','');  
                }  
            }  
  
            //如果沒有可以加的紅包,需要結(jié)束,否則死循環(huán) 
            //也就是會(huì)出現(xiàn)每個(gè)紅包不分錢的情況,比如紅包都已經(jīng)最大值。這時(shí)必須在分的時(shí)候給予標(biāo)志,防止死循環(huán)。 
            if($found == 0)  
            {  
                break;  
            }  
        }  
        //如果$leftMoney < 0 ,說明生成的紅包超過預(yù)算了,需要減少部分紅包金額  
        while($leftMoney < 0)  
        {  
            $found = 0;  
            foreach($data as $key => $val)   
            {  
                if($leftMoney >= 0)  
                {  
                    break;   
                }  
                //預(yù)判  
                $afterLeftMoney =  (double)$leftMoney + 0.01;  
                $afterVal = (double)$val - 0.01;  
                if( $afterLeftMoney <= 0 && $afterVal >= $this->minMoney)  
                {  
                    $found = 1;  
                    $data[$key] = number_format($afterVal,2,'.','');  
                    $leftMoney = $afterLeftMoney;  
                    $leftMoney = number_format($leftMoney,2,'.','');  
                }  
            }  
              
            //如果一個(gè)減少的紅包都沒有的話,需要結(jié)束,否則死循環(huán)  
            if($found == 0)  
            {  
                break;  
            }  
        }  
        return array($leftMoney,$data);    
    }
  
}
  
//維護(hù)策略的環(huán)境類  
class RedPackageBuilder  
{
    // 實(shí)例    
    protected static $_instance = null;    
  
    /**  
     * Singleton instance(獲取自己的實(shí)例)  
     *  
     * @return MemcacheOperate  
     */    
    public static function getInstance()  
    { 
        if (null === self::$_instance)   
        {    
            self::$_instance = new self();    
        }    
        return self::$_instance;    
    }
  
    /**  
     * 獲取策略【使用反射】 
     *  
     * @param string $type 類型  
     * @return void  
     */    
    public function getBuilderStrategy($type)  
    {  
        $class = $type.'PackageStrategy';  
  
        if(class_exists($class))  
        {  
            return new $class();    
        }  
        else  
        {  
            throw new Exception("{$class} 類不存在!");  
        }  
    } 
  
    public function getRedPackageByDTO(OptionDTO $optionDTO)   
    {
        //獲取策略  
        $builderStrategy = $this->getBuilderStrategy($optionDTO->builderStrategy);  
        //設(shè)置參數(shù)  
        $builderStrategy->setOption($optionDTO);  
  
        return $builderStrategy->create();  
    }
      
}
  
class Client  
{
    public static function main($argv)  
    {  
        //固定紅包  
        $dto = OptionDTO::create(1000,10,100,100,'Equal');  
        $data = RedPackageBuilder::getInstance()->getRedPackageByDTO($dto);  
        //print_r($data);  
  
        //隨機(jī)紅包[修數(shù)據(jù)]  
        $dto = OptionDTO::create(5,10,0.01,0.99,'RandTriangle');  
        $data = RedPackageBuilder::getInstance()->getRedPackageByDTO($dto);  
        print_r($data);  
  
        //隨機(jī)紅包[不修數(shù)據(jù)]  
        $dto = OptionDTO::create(5,10,0.01,0.99,'RandTriangle','Can_Left');  
        $data = RedPackageBuilder::getInstance()->getRedPackageByDTO($dto);  
        //print_r($data);      
    }  
}

$argv = [];
Client::main($argv);
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容