• 技术文章 >后端开发 >php教程

    php如何实现固定红包以及随机红包算法详解(图)

    黄舟黄舟2017-07-17 15:47:17原创2919

    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);
     print_r($data);


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



    以上就是php如何实现固定红包以及随机红包算法详解(图)的详细内容,更多请关注php中文网其它相关文章!

    声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn核实处理。
    专题推荐:php 以及 红包
    上一篇:php关于编码规范的文档(收藏) 下一篇:php如何将并发加锁的示例代码
    20期PHP线上班

    相关文章推荐

    • 【活动】充值PHP中文网VIP即送云服务器• 求教 Apache怎么配置Rewrite! • PHP登录跳转,该怎么处理 • JPGRAPH 只能显示6个字符有关问题 • php验证session 报错了解决办法 • mysql 线程有关问题,请问大侠们,顶者有分
    1/1

    PHP中文网