原文地址: 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 源码设计
-
-
-
-
-
-
-
- class OptionDTO
- {
-
-
- public $totalMoney;
-
-
- public $num;
-
-
- public $rangeStart;
-
-
- public $rangeEnd;
-
-
- public $builderStrategy;
-
-
- public $randFormatType;
-
- 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;
- }
-
-
-
-
-
-
-
-
- public function fx($x)
- {
- return $this->oneMoney;
- }
-
-
-
-
-
-
-
- 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;
- }
-
-
- if($this->oneMoney < 0.01)
- {
- return false;
- }
-
- return true;
-
- }
-
-
- }
-
-
- class RandTrianglePackageStrategy implements IBuilderStrategy
- {
-
- public $totalMoney;
-
-
- public $num;
-
-
- public $minMoney;
-
-
- public $maxMoney;
-
-
- 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;
- }
-
-
-
-
-
-
-
- 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;
- }
-
-
-
-
-
-
-
- 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;
-
- }
-
-
-
-
-
-
-
- public function getLeftMoney()
- {
- return $this->leftMoney;
- }
-
-
-
-
-
-
-
-
- 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;
-
-
- 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;
-
-
- }
-
-
-
-
-
-
-
-
-
- private function format($leftMoney,array $data)
- {
-
-
- if(false == $this->isCanBuilder())
- {
- return array($leftMoney,$data);
- }
-
-
- if(0 == $leftMoney)
- {
- return array($leftMoney,$data);
- }
-
-
- if(count($data) < 1)
- {
- return array($leftMoney,$data);
- }
-
-
- 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;
- }
- }
-
- 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;
-
-
-
-
-
-
- public static function getInstance()
- {
- if (null === self::$_instance)
- {
- self::$_instance = new self();
- }
- return self::$_instance;
- }
-
-
-
-
-
-
-
- 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);
-
-
-
- $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);
-
-
- }
- }
-
- 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);