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))
固定の赤いエンベロープの場合、アルゴリズムは直線になります。 t は赤い封筒の固定量です。写真の通り。
f(x) = t; (1
ランダム関数 rand を使用する場合。 rand(0.01,0.99); ランダムに 10 回実行すると、最悪のシナリオの金額が 0.99 の場合、合計金額は 9.9 元になります。 5元以上になります。金額も正規に分配されません。最後に、ランダムな赤いエンベロープジェネレーターとして数学関数を使用することを考えました。放物線と三角関数が使用できます。最後に、二等辺三角関数の一次関数が選択されました。
1 アルゴリズム原理
送信する赤い封筒の合計金額を totalMoney、赤い封筒の数を num、金額範囲を [min,max] とすると、一次方程式は図のようになります。 。
3 点の座標:
(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 は、> totalMoney である可能性があります。 、発生量が多すぎるためデータを修正する必要があることを示し、(y1、y2、y3...ynum) から 0.01 ずつ減らされます。 y(合計) = totalMoney になるまで。
y (一緒に) は
2 アルゴリズム原理の例
配布する赤い封筒の総量が11470、赤い封筒の数が7400、金額範囲が[0.01, 3.09]の場合、一次方程式は次のようになります図の中で。<?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);
1 赤い封筒の修正rrreええ
2ランダムな赤い封筒 (データを変更します)
ここでは PHP のランダム ソート関数 shuffle($okData) が使用されているため、表示される結果は線形ではなく、よりランダムになります。//固定红包 $dto = OptionDTO::create(1000,10,100,100,'Equal'); $data = RedPackageBuilder::getInstance()->getRedPackageByDTO($dto); print_r($data);
ランダムな赤い封筒 3 枚 (データ変更なし)
データ変更なし、1 と num の量は最小値 0.01 です。えー
以上がPHP が固定赤エンベロープとランダム赤エンベロープ アルゴリズムを実装する方法の詳細な説明 (図)の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。