微信随机红包数详解和算法代码

  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 源码设计


  2. <?php  
  3. /** 
  4.  * 随机红包+固定红包算法[策略模式] 
  5.  * copyright (c) 2016 http://blog.csdn.net/CleverCode 
  6.  */  
  7.   
  8. //配置传输数据DTO  
  9. class OptionDTO  
  10. {/*{{{*/  
  11.   
  12.     //红包总金额  
  13.     public $totalMoney;  
  14.   
  15.     //红包数量  
  16.     public $num;  
  17.   
  18.     //范围开始  
  19.     public $rangeStart;  
  20.   
  21.     //范围结算  
  22.     public $rangeEnd;  
  23.   
  24.     //生成红包策略  
  25.     public $builderStrategy;  
  26.   
  27.     //随机红包剩余规则  
  28.     public $randFormatType//Can_Left:不修数据,可以有剩余;No_Left:不能有剩余  
  29.   
  30.     public static function create($totalMoney,$num,$rangeStart,$rangEnd,  
  31.         $builderStrategy,$randFormatType = 'No_Left')  
  32.     {/*{{{*/  
  33.         $self = new self();  
  34.         $self->num = $num;  
  35.         $self->rangeStart = $rangeStart;  
  36.         $self->rangeEnd = $rangEnd;  
  37.         $self->totalMoney = $totalMoney;  
  38.         $self->builderStrategy = $builderStrategy;  
  39.         $self->randFormatType = $randFormatType;  
  40.         return $self;   
  41.     }/*}}}*/  
  42.   
  43. }/*}}}*/  
  44.   
  45. //红包生成器接口  
  46. interface IBuilderStrategy  
  47. {/*{{{*/  
  48.     //创建红包  
  49.     public function create();      
  50.     //设置配置  
  51.     public function setOption(OptionDTO $option);   
  52.     //是否可以生成红包  
  53.     public function isCanBuilder();  
  54.     //生成红包函数  
  55.     public function fx($x);  
  56. }/*}}}*/  
  57.   
  58. //固定等额红包策略  
  59. class EqualPackageStrategy implements IBuilderStrategy  
  60. {/*{{{*/  
  61.     //单个红包金额  
  62.     public $oneMoney;  
  63.   
  64.     //数量  
  65.     public $num;  
  66.   
  67.     public function __construct($option = null)   
  68.     {  
  69.         if($option instanceof OptionDTO)  
  70.         {  
  71.             $this->setOption($option);  
  72.         }  
  73.     }  
  74.   
  75.     public function setOption(OptionDTO $option)  
  76.     {  
  77.         $this->oneMoney = $option->rangeStart;  
  78.         $this->num = $option->num;  
  79.     }  
  80.   
  81.     public function create()   
  82.     {/*{{{*/  
  83.   
  84.         $data = array();  
  85.         if(false == $this->isCanBuilder())  
  86.         {  
  87.             return $data;      
  88.         }  
  89.   
  90.         $data = array();  
  91.         if(false == is_int($this->num) || $this->num <= 0)   
  92.         {  
  93.             return $data;      
  94.         }  
  95.         for($i = 1;$i <= $this->num;$i++)  
  96.         {  
  97.             $data[$i] = $this->fx($i);  
  98.         }  
  99.         return $data;  
  100.     }/*}}}*/  
  101.       
  102.     /** 
  103.      * 等额红包的方程是一条直线  
  104.      *  
  105.      * @param mixed $x  
  106.      * @access public 
  107.      * @return void 
  108.      */  
  109.     public function fx($x)   
  110.     {/*{{{*/  
  111.         return $this->oneMoney;   
  112.     }/*}}}*/  
  113.   
  114.     /** 
  115.      * 是否能固定红包  
  116.      *  
  117.      * @access public 
  118.      * @return void 
  119.      */  
  120.     public function isCanBuilder()  
  121.     {/*{{{*/  
  122.         if(false == is_int($this->num) || $this->num <= 0)   
  123.         {  
  124.             return false;      
  125.         }  
  126.   
  127.         if(false ==  is_numeric($this->oneMoney) || $this->oneMoney <= 0)  
  128.         {  
  129.             return false;  
  130.         }  
  131.   
  132.         //单个红包小于1分  
  133.         if($this->oneMoney < 0.01)  
  134.         {  
  135.             return false;  
  136.         }  
  137.           
  138.         return true;  
  139.   
  140.     }/*}}}*/  
  141.   
  142.   
  143. }/*}}}*/  
  144.   
  145. //随机红包策略(三角形)  
  146. class RandTrianglePackageStrategy implements IBuilderStrategy  
  147. {/*{{{*/  
  148.     //总额  
  149.     public $totalMoney;  
  150.   
  151.     //红包数量  
  152.     public $num;  
  153.   
  154.     //随机红包最小值  
  155.     public $minMoney;  
  156.   
  157.     //随机红包最大值  
  158.     public $maxMoney;  
  159.   
  160.     //修数据方式:NO_LEFT: 红包总额 = 预算总额;CAN_LEFT: 红包总额 <= 预算总额  
  161.     public $formatType;   
  162.   
  163.     //预算剩余金额  
  164.     public $leftMoney;  
  165.   
  166.   
  167.     public function __construct($option = null)   
  168.     {/*{{{*/  
  169.         if($option instanceof OptionDTO)  
  170.         {  
  171.             $this->setOption($option);  
  172.         }  
  173.     }/*}}}*/  
  174.   
  175.     public function setOption(OptionDTO $option)  
  176.     {/*{{{*/  
  177.         $this->totalMoney = $option->totalMoney;  
  178.         $this->num = $option->num;  
  179.         $this->formatType = $option->randFormatType;  
  180.         $this->minMoney = $option->rangeStart;  
  181.         $this->maxMoney = $option->rangeEnd;  
  182.         $this->leftMoney = $this->totalMoney;  
  183.     }/*}}}*/  
  184.   
  185.     /** 
  186.      * 创建随机红包  
  187.      *  
  188.      * @access public 
  189.      * @return void 
  190.      */  
  191.     public function create()   
  192.     {/*{{{*/  
  193.           
  194.         $data = array();  
  195.         if(false == $this->isCanBuilder())  
  196.         {  
  197.             return $data;      
  198.         }  
  199.           
  200.         $leftMoney = $this->leftMoney;  
  201.         for($i = 1;$i <= $this->num;$i++)  
  202.         {  
  203.             $data[$i] = $this->fx($i);  
  204.             $leftMoney = $leftMoney - $data[$i];   
  205.         }  
  206.   
  207.         //修数据  
  208.         list($okLeftMoney,$okData) = $this->format($leftMoney,$data);  
  209.   
  210.         //随机排序  
  211.         shuffle($okData);  
  212.         $this->leftMoney = $okLeftMoney;  
  213.   
  214.         return $okData;  
  215.     }/*}}}*/  
  216.   
  217.     /** 
  218.      * 是否能够发随机红包  
  219.      *  
  220.      * @access public 
  221.      * @return void 
  222.      */  
  223.     public function isCanBuilder()  
  224.     {/*{{{*/  
  225.         if(false == is_int($this->num) || $this->num <= 0)   
  226.         {  
  227.             return false;      
  228.         }  
  229.   
  230.         if(false ==  is_numeric($this->totalMoney) || $this->totalMoney <= 0)  
  231.         {  
  232.             return false;  
  233.         }  
  234.   
  235.         //均值  
  236.         $avgMoney = $this->totalMoney / 1.0 / $this->num;  
  237.           
  238.         //均值小于最小值  
  239.         if($avgMoney < $this->minMoney )  
  240.         {  
  241.             return false;  
  242.         }  
  243.           
  244.         return true;  
  245.   
  246.     }/*}}}*/  
  247.   
  248.     /** 
  249.      * 获取剩余金额  
  250.      *  
  251.      * @access public 
  252.      * @return void 
  253.      */  
  254.     public function getLeftMoney()  
  255.     {/*{{{*/  
  256.         return $this->leftMoney;  
  257.     }/*}}}*/  
  258.   
  259.     /** 
  260.      * 随机红包生成函数。三角函数。[(1,0.01),($num/2,$avgMoney),($num,0.01)]  
  261.      *  
  262.      * @param mixed $x,1 <= $x <= $this->num;  
  263.      * @access public 
  264.      * @return void 
  265.      */  
  266.     public function fx($x)  
  267.     {/*{{{*/  
  268.           
  269.         if(false == $this->isCanBuilder())  
  270.         {  
  271.             return 0;  
  272.         }  
  273.   
  274.         if($x < 1 || $x > $this->num)  
  275.         {  
  276.             return 0;  
  277.         }  
  278.           
  279.         $x1 = 1;  
  280.         $y1 = $this->minMoney;  
  281.           
  282.         //我的峰值  
  283.         $y2 = $this->maxMoney;  
  284.   
  285.         //中间点  
  286.         $x2 = ceil($this->num /  1.0 / 2);  
  287.   
  288.         //最后点  
  289.         $x3 = $this->num;  
  290.         $y3 = $this->minMoney;    
  291.   
  292.         //当x1,x2,x3都是1的时候(竖线)  
  293.         if($x1 == $x2 && $x2 == $x3)  
  294.         {  
  295.             return $y2;  
  296.         }  
  297.   
  298.         // '/_\'三角形状的线性方程  
  299.         //'/'部分  
  300.         if($x1 != $x2 && $x >= $x1 && $x <= $x2)  
  301.         {  
  302.   
  303.             $y = 1.0 * ($x - $x1) / ($x2 - $x1) * ($y2 - $y1) + $y1;    
  304.             return number_format($y, 2, '.''');  
  305.         }  
  306.   
  307.         //'\'形状  
  308.         if($x2 != $x3 && $x >= $x2 && $x <= $x3)  
  309.         {  
  310.   
  311.             $y = 1.0 * ($x - $x2) / ($x3 - $x2) * ($y3 - $y2) + $y2;    
  312.             return number_format($y, 2, '.''');  
  313.         }  
  314.           
  315.         return 0;  
  316.   
  317.   
  318.     }/*}}}*/  
  319.   
  320.     /** 
  321.      * 格式化修红包数据  
  322.      *  
  323.      * @param mixed $leftMoney  
  324.      * @param array $data  
  325.      * @access public 
  326.      * @return void 
  327.      */  
  328.     private function format($leftMoney,array $data)  
  329.     {/*{{{*/  
  330.   
  331.         //不能发随机红包  
  332.         if(false == $this->isCanBuilder())  
  333.         {  
  334.             return array($leftMoney,$data);    
  335.         }  
  336.           
  337.         //红包剩余是0  
  338.         if(0 == $leftMoney)  
  339.         {  
  340.             return array($leftMoney,$data);    
  341.         }  
  342.   
  343.         //数组为空  
  344.         if(count($data) < 1)  
  345.         {  
  346.             return array($leftMoney,$data);    
  347.         }  
  348.   
  349.         //如果是可以有剩余,并且$leftMoney > 0  
  350.         if('Can_Left' == $this->formatType  
  351.           && $leftMoney > 0)  
  352.         {  
  353.             return array($leftMoney,$data);    
  354.         }  
  355.   
  356.   
  357.         //我的峰值  
  358.         $myMax = $this->maxMoney;  
  359.   
  360.         // 如果还有余钱,则尝试加到小红包里,如果加不进去,则尝试下一个。  
  361.         while($leftMoney > 0)  
  362.         {  
  363.             $found = 0;  
  364.             foreach($data as $key => $val)   
  365.             {  
  366.                 //减少循环优化  
  367.                 if($leftMoney <= 0)  
  368.                 {  
  369.                     break;  
  370.                 }  
  371.   
  372.                 //预判  
  373.                 $afterLeftMoney =  (double)$leftMoney - 0.01;  
  374.                 $afterVal = (double)$val + 0.01;  
  375.                 if$afterLeftMoney >= 0  && $afterVal <= $myMax)  
  376.                 {  
  377.                     $found = 1;  
  378.                     $data[$key] = number_format($afterVal,2,'.','');  
  379.                     $leftMoney = $afterLeftMoney;  
  380.                     //精度  
  381.                     $leftMoney = number_format($leftMoney,2,'.','');  
  382.                 }  
  383.             }  
  384.   
  385.             //如果没有可以加的红包,需要结束,否则死循环  
  386.             if($found == 0)  
  387.             {  
  388.                 break;  
  389.             }  
  390.         }  
  391.         //如果$leftMoney < 0 ,说明生成的红包超过预算了,需要减少部分红包金额  
  392.         while($leftMoney < 0)  
  393.         {  
  394.             $found = 0;  
  395.             foreach($data as $key => $val)   
  396.             {  
  397.                 if($leftMoney >= 0)  
  398.                 {  
  399.                     break;   
  400.                 }  
  401.                 //预判  
  402.                   
  403.                 $afterLeftMoney =  (double)$leftMoney + 0.01;  
  404.                 $afterVal = (double)$val - 0.01;  
  405.                 if$afterLeftMoney <= 0 && $afterVal >= $this->minMoney)  
  406.                 {  
  407.                     $found = 1;  
  408.                     $data[$key] = number_format($afterVal,2,'.','');  
  409.                     $leftMoney = $afterLeftMoney;  
  410.                     $leftMoney = number_format($leftMoney,2,'.','');  
  411.                 }  
  412.             }  
  413.               
  414.             //如果一个减少的红包都没有的话,需要结束,否则死循环  
  415.             if($found == 0)  
  416.             {  
  417.                 break;  
  418.             }  
  419.         }  
  420.         return array($leftMoney,$data);    
  421.     }/*}}}*/  
  422.   
  423. }/*}}}*/  
  424.   
  425. //维护策略的环境类  
  426. class RedPackageBuilder  
  427. {/*{{{*/  
  428.   
  429.     // 实例    
  430.     protected static $_instance = null;    
  431.   
  432.     /**  
  433.      * Singleton instance(获取自己的实例)  
  434.      *  
  435.      * @return MemcacheOperate  
  436.      */    
  437.     public static function getInstance()  
  438.     {  /*{{{*/  
  439.         if (null === self::$_instance)   
  440.         {    
  441.             self::$_instance = new self();    
  442.         }    
  443.         return self::$_instance;    
  444.     }  /*}}}*/  
  445.   
  446.     /**  
  447.      * 获取策略【使用反射】 
  448.      *  
  449.      * @param string $type 类型  
  450.      * @return void  
  451.      */    
  452.     public function getBuilderStrategy($type)  
  453.     {  /*{{{*/  
  454.         $class = $type.'PackageStrategy';  
  455.   
  456.         if(class_exists($class))  
  457.         {  
  458.             return new $class();    
  459.         }  
  460.         else  
  461.         {  
  462.             throw new Exception("{$class} 类不存在!");  
  463.         }  
  464.     }  /*}}}*/  
  465.   
  466.     public function getRedPackageByDTO(OptionDTO $optionDTO)   
  467.     {/*{{{*/  
  468.         //获取策略  
  469.         $builderStrategy = $this->getBuilderStrategy($optionDTO->builderStrategy);  
  470.   
  471.         //设置参数  
  472.         $builderStrategy->setOption($optionDTO);  
  473.   
  474.         return $builderStrategy->create();  
  475.     }/*}}}*/  
  476.       
  477. }/*}}}*/  
  478.   
  479. class Client  
  480. {/*{{{*/  
  481.     public static function main($argv)  
  482.     {  
  483.         //固定红包  
  484.         $dto = OptionDTO::create(1000,10,100,100,'Equal');  
  485.         $data = RedPackageBuilder::getInstance()->getRedPackageByDTO($dto);  
  486.         //print_r($data);  
  487.   
  488.         //随机红包[修数据]  
  489.         $dto = OptionDTO::create(5,10,0.01,0.99,'RandTriangle');  
  490.         $data = RedPackageBuilder::getInstance()->getRedPackageByDTO($dto);  
  491.         print_r($data);  
  492.   
  493.         //随机红包[不修数据]  
  494.         $dto = OptionDTO::create(5,10,0.01,0.99,'RandTriangle','Can_Left');  
  495.         $data = RedPackageBuilder::getInstance()->getRedPackageByDTO($dto);  
  496.         //print_r($data);  
  497.           
  498.     }  
  499. }/*}}}*/  
  500.   
  501. Client::main($argv);  
  • 10
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值