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



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值