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

来源:互联网 发布:炒股软件免费版排名 编辑:程序博客网 时间:2024/05/16 08:45
  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);  
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 万家乐燃气热水器说明书 万家乐热水器说明书图片 万家乐燃气热水器电话 万家乐油烟机怎么样 万和和万家乐热水器哪个好 万家乐厨房电器怎么样 万家乐60升电热水器 万家乐50升电热水器 万家乐燃气热水器怎么用 万家乐燃气热水器怎么用图解 广东万家乐燃气具有限公司 万家乐热水器服务电话 万家乐电热水器插头 万家乐热水器打火打不着 万家乐燃气热水器安装 万家乐燃气灶松手熄火 万家乐电热水器服务电话 万家乐售后维修电话 万家乐燃气热水器好吗 万家乐燃气热水器16 万家乐电热水器价格表 万家乐电热水器好不好 万家乐和万和燃气灶哪个好 万家乐热水器服务热线 万家乐灶具质量怎么样 万家乐天然气热水器打不着火 万家乐燃气灶多少钱 万家乐燃气热水器e4 万家乐燃气热水器多少钱 万家乐燃气热水器12 万家乐燃气热水器服务电话 万家乐电热水器多少钱 万家乐燃气热水器忽冷忽热 万家乐电热水器质量怎么样 万家乐热水器打不着火的原因 万家乐灶具维修电话 万家乐燃气热水器排行 万家乐电热水器不加热 万家乐燃气热水器uf1 万家乐吸油烟机怎么样 万家乐热水器点不着火