算法为自创,非微信官方算法(仅供参考);如测出有问题,请及时反馈。感谢
原文:http://flc.ren/2018/04/701.html
CHANGELOG
2018-04-07
- 修复最后一个红包输出未保留2位数
- 修复领取的红包金额低于最小红包限制
<?php
/**
* 红包分配算法
*
* @example
* $coupon = new Coupon(200, 5);
* $res = $coupon->handle();
* print_r($res);
*
* @author Flc <2018-04-06 20:09:53>
* @see http://flc.ren | http://flc.io | https://github.com/flc1125
*/
class Coupon
{
/**
* 红包金额
*
* @var float
*/
protected $amount;
/**
* 红包个数
*
* @var int
*/
protected $num;
/**
* 领取的红包最小金额
*
* @var float
*/
protected $coupon_min;
/**
* 红包分配结果
*
* @var array
*/
protected $items = [];
/**
* 初始化
*
* @param float $amount 红包金额(单位:元)最多保留2位小数
* @param int $num 红包个数
* @param float $coupon_min 每个至少领取的红包金额
*/
public function __construct($amount, $num = 1, $coupon_min = 0.01)
{
$this->amount = $amount;
$this->num = $num;
$this->coupon_min = $coupon_min;
}
/**
* 处理返回
*
* @return array
*/
public function handle()
{
// A. 验证
if ($this->amount < $validAmount = $this->coupon_min * $this->num) {
throw new Exception('红包总金额必须≥'.$validAmount.'元');
}
// B. 分配红包
$this->apportion();
return [
'items' => $this->items,
];
}
/**
* 分配红包
*/
protected function apportion()
{
$num = $this->num; // 剩余可分配的红包个数
$amount = $this->amount; //剩余可领取的红包金额
while ($num >= 1) {
// 剩余一个的时候,直接取剩余红包
if ($num == 1) {
$coupon_amount = $this->decimal_number($amount);
} else {
$avg_amount = $this->decimal_number($amount / $num); // 剩余的红包的平均金额
$coupon_amount = $this->decimal_number(
$this->calcCouponAmount($avg_amount, $amount, $num)
);
}
$this->items[] = $coupon_amount; // 追加分配
$amount -= $coupon_amount;
--$num;
}
shuffle($this->items); //随机打乱
}
/**
* 计算分配的红包金额
*
* @param float $avg_amount 每次计算的平均金额
* @param float $amount 剩余可领取金额
* @param int $num 剩余可领取的红包个数
*
* @return float
*/
protected function calcCouponAmount($avg_amount, $amount, $num)
{
// 如果平均金额小于等于最低金额,则直接返回最低金额
if ($avg_amount <= $this->coupon_min) {
return $this->coupon_min;
}
// 浮动计算
$coupon_amount = $this->decimal_number($avg_amount * (1 + $this->apportionRandRatio()));
// 如果低于最低金额或超过可领取的最大金额,则重新获取
if ($coupon_amount < $this->coupon_min
|| $coupon_amount > $this->calcCouponAmountMax($amount, $num)
) {
return $this->calcCouponAmount($avg_amount, $amount, $num);
}
return $coupon_amount;
}
/**
* 计算分配的红包金额-可领取的最大金额
*
* @param float $amount
* @param int $num
*/
protected function calcCouponAmountMax($amount, $num)
{
return $this->coupon_min + $amount - $num * $this->coupon_min;
}
/**
* 红包金额浮动比例
*/
protected function apportionRandRatio()
{
// 60%机率获取剩余平均值的大幅度红包(可能正数、可能负数)
if (rand(1, 100) <= 60) {
return rand(-70, 70) / 100; // 上下幅度70%
}
return rand(-30, 30) / 100; // 其他情况,上下浮动30%;
}
/**
* 格式化金额,保留2位
*
* @param float $amount
*
* @return float
*/
protected function decimal_number($amount)
{
return sprintf('%01.2f', round($amount, 2));
}
}
// 例子
$coupon = new Coupon(200, 5, 30);
$res = $coupon->handle();
print_r($res);
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。