微信紅包算法

原文鏈接:http://blog.csdn.net/CleverCode

微信紅包算法

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

        //中間點
        $x2 = ceil($this->num / 1.0 / 2);
        //我的峯值
        $y2 = $this->maxMoney;

        //最後點
        $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 );
        }

        // 如果還有餘錢,則嘗試加到小紅包裏,如果加不進去,則嘗試下一個。
        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->maxMoney) {
                    $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);
        echo "<br/>";

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

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

$argv = [];
Client::main($argv);

 

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