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如何实现固定红包以及随机红包算法详解(图)的详细内容。