[算法] -- php固定红包 + 随机红包算法

原文地址: http://blog.csdn.net/clevercode/article/details/53239681


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