php固定紅包 + 隨機紅包算法

(本文系轉載,僅用於學習,不做任何商業運用,如有冒犯,請告知刪除)

1 需求

CleverCode最近接到一個需求,需要寫一個固定紅包 + 隨機紅包算法。
1 固定紅包就是每個紅包金額一樣,有多少個就發多少個固定紅包金額就行。
2 隨機紅包的需求是。比如紅包總金額5元,需要發10個紅包。隨機範圍是 0.01到0.99;5元必需發完,金額需要有一定趨勢的正態分佈。(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)
這裏寫圖片描述
2.2 隨機紅包

如果我們使用隨機函數rand。rand(0.01,0.99);那麼10次隨機,如果最壞情況都是金額0.99,總金額就是9.9元。會超過5元。金額也會不正態分佈。最後思考了一下藉助與數學函數來當作隨機紅包的發生器,可以用拋物線,三角函數。最後選定了等腰三角線性函數。

1 算法原理
如果需要發紅包總金額是totalMoney,紅包個數是num個,金額範圍是[min,max],線性方程如圖。
這裏寫圖片描述
三個點的座標:
(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)
修數據:
y(合) = y1 + y2 + y3 +…… ynum;
y(合)有可能 > totalMoney ,說明生成金額多了,需要修數據,則從(y1,y2,y3…..ynum)這些每次減少0.01。直到y(合) = totalMoney。
y(合)有可能 < totalMoney ,說明生成金額少了,需要修數據,則從(y1,y2,y3…..ynum)這些每次加上0.01。直到y(合) = totalMoney。

2 算法原理樣例
如果需要發紅包總金額是11470,紅包個數是7400個,金額範圍是[0.01,3.09],線性方程如圖。
這裏寫圖片描述
3 需求設計

3.1 類圖設計
這裏寫圖片描述
3.2 源碼設計

<?php  
/** 
 * 隨機紅包+固定紅包算法[策略模式] 
 * copyright (c) 2016 http://blog.csdn.net/CleverCode 
 */  

//配置傳輸數據DTO  
class OptionDTO  
{/*{{{*/  

    //紅包總金額  
    public $totalMoney;  

    //紅包數量  
    public $num;  

    //範圍開始  
    public $rangeStart;  

    //範圍結算  
    public $rangeEnd;  

    //生成紅包策略  
    public $builderStrategy;  

    //隨機紅包剩餘規則  
    public $randFormatType; //Can_Left:不修數據,可以有剩餘;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  
{/*{{{*/  
    //創建紅包  
    public function create();      
    //設置配置  
    public function setOption(OptionDTO $option);   
    //是否可以生成紅包  
    public function isCanBuilder();  
    //生成紅包函數  
    public function fx($x);  
}/*}}}*/  

//固定等額紅包策略  
class EqualPackageStrategy implements IBuilderStrategy  
{/*{{{*/  
    //單個紅包金額  
    public $oneMoney;  

    //數量  
    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;  
        }  

        //單個紅包小於1分  
        if($this->oneMoney < 0.01)  
        {  
            return false;  
        }  

        return true;  

    }/*}}}*/  


}/*}}}*/  

//隨機紅包策略(三角形)  
class RandTrianglePackageStrategy implements IBuilderStrategy  
{/*{{{*/  
    //總額  
    public $totalMoney;  

    //紅包數量  
    public $num;  

    //隨機紅包最小值  
    public $minMoney;  

    //隨機紅包最大值  
    public $maxMoney;  

    //修數據方式:NO_LEFT: 紅包總額 = 預算總額;CAN_LEFT: 紅包總額 <= 預算總額  
    public $formatType;   

    //預算剩餘金額  
    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;  
    }/*}}}*/  

    /** 
     * 創建隨機紅包  
     *  
     * @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];   
        }  

        //修數據  
        list($okLeftMoney,$okData) = $this->format($leftMoney,$data);  

        //隨機排序  
        shuffle($okData);  
        $this->leftMoney = $okLeftMoney;  

        return $okData;  
    }/*}}}*/  

    /** 
     * 是否能夠發隨機紅包  
     *  
     * @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;  
    }/*}}}*/  

    /** 
     * 隨機紅包生成函數。三角函數。[(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;  

        //我的峯值  
        $y2 = $this->maxMoney;  

        //中間點  
        $x2 = ceil($this->num /  1.0 / 2);  

        //最後點  
        $x3 = $this->num;  
        $y3 = $this->minMoney;    

        //當x1,x2,x3都是1的時候(豎線)  
        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;  


    }/*}}}*/  

    /** 
     * 格式化修紅包數據  
     *  
     * @param mixed $leftMoney  
     * @param array $data  
     * @access public 
     * @return void 
     */  
    private function format($leftMoney,array $data)  
    {/*{{{*/  

        //不能發隨機紅包  
        if(false == $this->isCanBuilder())  
        {  
            return array($leftMoney,$data);    
        }  

        //紅包剩餘是0  
        if(0 == $leftMoney)  
        {  
            return array($leftMoney,$data);    
        }  

        //數組爲空  
        if(count($data) < 1)  
        {  
            return array($leftMoney,$data);    
        }  

        //如果是可以有剩餘,並且$leftMoney > 0  
        if('Can_Left' == $this->formatType  
          && $leftMoney > 0)  
        {  
            return array($leftMoney,$data);    
        }  


        //我的峯值  
        $myMax = $this->maxMoney;  

        // 如果還有餘錢,則嘗試加到小紅包裏,如果加不進去,則嘗試下一個。  
        while($leftMoney > 0)  
        {  
            $found = 0;  
            foreach($data as $key => $val)   
            {  
                //減少循環優化  
                if($leftMoney <= 0)  
                {  
                    break;  
                }  

                //預判  
                $afterLeftMoney =  (double)$leftMoney - 0.01;  
                $afterVal = (double)$val + 0.01;  
                if( $afterLeftMoney >= 0  && $afterVal <= $myMax)  
                {  
                    $found = 1;  
                    $data[$key] = number_format($afterVal,2,'.','');  
                    $leftMoney = $afterLeftMoney;  
                    //精度  
                    $leftMoney = number_format($leftMoney,2,'.','');  
                }  
            }  

            //如果沒有可以加的紅包,需要結束,否則死循環  
            if($found == 0)  
            {  
                break;  
            }  
        }  
        //如果$leftMoney < 0 ,說明生成的紅包超過預算了,需要減少部分紅包金額  
        while($leftMoney < 0)  
        {  
            $found = 0;  
            foreach($data as $key => $val)   
            {  
                if($leftMoney >= 0)  
                {  
                    break;   
                }  
                //預判  

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

            //如果一個減少的紅包都沒有的話,需要結束,否則死循環  
            if($found == 0)  
            {  
                break;  
            }  
        }  
        return array($leftMoney,$data);    
    }/*}}}*/  

}/*}}}*/  

//維護策略的環境類  
class RedPackageBuilder  
{/*{{{*/  

    // 實例    
    protected static $_instance = null;    

    /**  
     * Singleton instance(獲取自己的實例)  
     *  
     * @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);  

        //設置參數  
        $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);  

        //隨機紅包[修數據]  
        $dto = OptionDTO::create(5,10,0.01,0.99,'RandTriangle');  
        $data = RedPackageBuilder::getInstance()->getRedPackageByDTO($dto);  
        print_r($data);  

        //隨機紅包[不修數據]  
        $dto = OptionDTO::create(5,10,0.01,0.99,'RandTriangle','Can_Left');  
        $data = RedPackageBuilder::getInstance()->getRedPackageByDTO($dto);  
        //print_r($data);  

    }  
}/*}}}*/  

Client::main($argv);  

3.3 結果展示

1 固定紅包
//固定紅包
$dto = OptionDTO::create(1000,10,100,100,’Equal’);
$data = RedPackageBuilder::getInstance()->getRedPackageByDTO($dto);

print_r($data);
這裏寫圖片描述

2 隨機紅包(修數據)
這裏使用了php的隨機排序函數, shuffle($okData),所以看到的結果不是線性的,這個結果更加隨機性。
//隨機紅包[修數據]
$dto = OptionDTO::create(5,10,0.01,0.99,’RandTriangle’);
$data = RedPackageBuilder::getInstance()->getRedPackageByDTO($dto);

這裏寫圖片描述

3 隨機紅包(不修數據)
不修數據,1 和num的金額是最小值0.01。
//隨機紅包[不修數據]
$dto = OptionDTO::create(5,10,0.01,0.99,’RandTriangle’,’Can_Left’);
$data = RedPackageBuilder::getInstance()->getRedPackageByDTO($dto);

print_r($data);
這裏寫圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章