本文目录导读:

我来为你提供一个PHP优惠分摊算法的完整实现,包含多种常见的分摊场景:
基本按比例分摊算法
<?php
/**
* 按比例分摊优惠金额
*
* @param array $items 商品列表 [['amount' => 100], ['amount' => 200]]
* @param float $discount 总优惠金额
* @return array 每个商品分摊的优惠金额
*/
function distributeDiscountProportionally(array $items, float $discount): array
{
$totalAmount = array_sum(array_column($items, 'amount'));
if ($totalAmount == 0 || $discount <= 0) {
return array_fill(0, count($items), 0);
}
$distributions = [];
$distributedTotal = 0;
foreach ($items as $index => $item) {
if ($index === count($items) - 1) {
// 最后一个商品获得剩余的优惠,避免因四舍五入产生误差
$distributions[$index] = round($discount - $distributedTotal, 2);
} else {
$distributed = round($discount * ($item['amount'] / $totalAmount), 2);
$distributions[$index] = $distributed;
$distributedTotal += $distributed;
}
}
return $distributions;
}
// 使用示例
$items = [
['id' => 1, 'amount' => 100],
['id' => 2, 'amount' => 200],
['id' => 3, 'amount' => 300]
];
$discount = 50;
$result = distributeDiscountProportionally($items, $discount);
print_r($result); // 输出: [8.33, 16.67, 25]
带优先级的优惠分摊(会员优先)
<?php
/**
* 带优先级的优惠分摊
*
* @param array $items 商品列表
* @param float $discount 总优惠金额
* @param string $priorityField 优先级字段
* @return array 分摊结果
*/
function distributeDiscountWithPriority(array $items, float $discount, string $priorityField = 'priority'): array
{
// 按优先级排序
$sortedItems = $items;
usort($sortedItems, function($a, $b) use ($priorityField) {
return $b[$priorityField] <=> $a[$priorityField];
});
$distributions = [];
$remainingDiscount = $discount;
foreach ($sortedItems as $index => $item) {
if ($index === count($sortedItems) - 1) {
// 最后一个承担所有剩余
$distributions[$item['id']] = $remainingDiscount;
} else {
// 高优先级先分摊
$distribute = min(
$item['amount'],
$remainingDiscount * $item['priority'] / 10
);
$distributions[$item['id']] = round($distribute, 2);
$remainingDiscount -= $distribute;
}
}
return $distributions;
}
// 使用示例
$items = [
['id' => 1, 'amount' => 100, 'priority' => 8], // 会员商品
['id' => 2, 'amount' => 200, 'priority' => 5], // 普通商品
['id' => 3, 'amount' => 300, 'priority' => 3] // 低优先级
];
$discount = 50;
$result = distributeDiscountWithPriority($items, $discount);
print_r($result);
考虑价格上限的分摊算法
<?php
/**
* 带价格上限的优惠分摊
*
* @param array $items 商品列表
* @param float $discount 总优惠金额
* @param float $maxDiscountPerItem 单件商品最大优惠
* @return array
*/
function distributeDiscountWithCap(array $items, float $discount, float $maxDiscountPerItem): array
{
$distributions = [];
$remainingDiscount = $discount;
// 先按比例初步分摊
$totalAmount = array_sum(array_column($items, 'amount'));
foreach ($items as $index => $item) {
$proportional = $discount * ($item['amount'] / $totalAmount);
$distributions[$index] = min($proportional, $maxDiscountPerItem);
$remainingDiscount -= $distributions[$index];
}
// 如果有剩余优惠,继续分摊给未达到上限的商品
while ($remainingDiscount > 0.01) {
$cappedCount = 0;
$remainingAmount = 0;
foreach ($items as $index => $item) {
if ($distributions[$index] < $maxDiscountPerItem) {
$cappedCount++;
$remainingAmount += $item['amount'];
}
}
if ($cappedCount === 0) break;
foreach ($items as $index => $item) {
if ($distributions[$index] < $maxDiscountPerItem) {
$additional = min(
$remainingDiscount * ($item['amount'] / $remainingAmount),
$maxDiscountPerItem - $distributions[$index]
);
$distributions[$index] += $additional;
$remainingDiscount -= $additional;
}
}
}
// 处理最后一位数差异
$totalDistributed = array_sum($distributions);
if (abs($totalDistributed - $discount) < 0.01) {
$diff = $discount - $totalDistributed;
$distributions[array_key_last($distributions)] += $diff;
}
return array_map('round', $distributions, array_fill(0, count($distributions), 2));
}
购物车级优惠分摊类
<?php
class DiscountDistributor
{
private array $items;
private float $totalDiscount;
public function __construct(array $items, float $totalDiscount)
{
$this->items = $items;
$this->totalDiscount = $totalDiscount;
}
/**
* 按商品金额比例分摊
*/
public function distributeByAmount(): array
{
return $this->proportionalDistribution('amount');
}
/**
* 按商品数量平均分摊(需提供数量字段)
*/
public function distributeEqually(string $quantityField = 'quantity'): array
{
$totalQuantity = array_sum(array_column($this->items, $quantityField));
$result = [];
foreach ($this->items as $index => $item) {
$result[$index] = round(
$this->totalDiscount * ($item[$quantityField] / $totalQuantity),
2
);
}
// 确保总和准确
$sum = array_sum($result);
$diff = $this->totalDiscount - $sum;
if ($diff != 0) {
$result[array_key_last($result)] += $diff;
}
return $result;
}
/**
* 自定义权重分摊
*/
public function distributeByWeight(callable $weightCallback): array
{
$weights = [];
$totalWeight = 0;
foreach ($this->items as $index => $item) {
$weight = $weightCallback($item);
$weights[$index] = $weight;
$totalWeight += $weight;
}
$result = [];
foreach ($weights as $index => $weight) {
$result[$index] = round($this->totalDiscount * ($weight / $totalWeight), 2);
}
// 精度校正
$sum = array_sum($result);
$diff = $this->totalDiscount - $sum;
if ($diff != 0) {
$result[array_key_last($result)] += $diff;
}
return $result;
}
/**
* 通用的比例分配方法
*/
private function proportionalDistribution(string $field): array
{
$total = array_sum(array_column($this->items, $field));
$result = [];
foreach ($this->items as $index => $item) {
if ($index === count($this->items) - 1) {
// 最后一个元素,用总优惠减去已分配的
$result[$index] = $this->totalDiscount - array_sum($result);
} else {
$result[$index] = round(
$this->totalDiscount * ($item[$field] / $total),
2
);
}
}
return $result;
}
}
// 使用示例
$items = [
['id' => 1, 'name' => '商品A', 'amount' => 100, 'quantity' => 1],
['id' => 2, 'name' => '商品B', 'amount' => 200, 'quantity' => 2],
['id' => 3, 'name' => '商品C', 'amount' => 300, 'quantity' => 1]
];
$distributor = new DiscountDistributor($items, 50);
// 按金额比例分摊
$byAmount = $distributor->distributeByAmount();
// 按数量分摊
$byQuantity = $distributor->distributeEqually();
// 自定义权重(比如按商品利润)
$byProfit = $distributor->distributeByWeight(function($item) {
// 假设利润率
return $item['amount'] * 0.2;
});
echo "按金额分摊: " . json_encode($byAmount) . "\n";
echo "按数量分摊: " . json_encode($byQuantity) . "\n";
echo "按利润分摊: " . json_encode($byProfit) . "\n";
高级多级优惠分摊(组合优惠)
<?php
/**
* 多级优惠分摊(如满减+折扣+优惠券)
*/
function multiLevelDiscountDistribution(array $items, array $discountRules): array
{
$result = [];
// 初始化每个商品的优惠
foreach ($items as $index => $item) {
$result[$index] = 0;
}
foreach ($discountRules as $rule) {
switch ($rule['type']) {
case 'full_reduction': // 满减
$discount = $rule['value'];
if (array_sum(array_column($items, 'amount')) >= $rule['threshold']) {
$distributions = distributeDiscountProportionally($items, $discount);
foreach ($distributions as $index => $dist) {
$result[$index] += $dist;
}
}
break;
case 'percentage': // 折扣
$rate = $rule['value']; // 如 0.9 表示9折
foreach ($items as $index => $item) {
$discount = $item['amount'] * (1 - $rate);
$result[$index] += $discount;
}
break;
case 'coupon': // 优惠券
$couponValue = $rule['value'];
$distributions = distributeDiscountProportionally($items, $couponValue);
foreach ($distributions as $index => $dist) {
$result[$index] += $dist;
}
break;
}
}
// 确保每个商品的优惠不超过其价格
foreach ($items as $index => $item) {
$result[$index] = min(round($result[$index], 2), $item['amount']);
}
return $result;
}
// 使用示例
$items = [
['id' => 1, 'name' => '商品A', 'amount' => 300],
['id' => 2, 'name' => '商品B', 'amount' => 200],
['id' => 3, 'name' => '商品C', 'amount' => 500]
];
$rules = [
['type' => 'full_reduction', 'threshold' => 500, 'value' => 50], // 满500减50
['type' => 'percentage', 'value' => 0.95], // 全场95折
['type' => 'coupon', 'value' => 30] // 优惠券30元
];
$result = multiLevelDiscountDistribution($items, $rules);
print_r($result);
验证与测试函数
<?php
/**
* 验证分摊结果
*/
function validateDistribution(array $items, array $distributions, float $totalDiscount): bool
{
// 检查1: 分摊总额是否正确
if (abs(array_sum($distributions) - $totalDiscount) > 0.01) {
echo "错误:分摊总额不符\n";
return false;
}
// 检查2: 每个商品的优惠不能超过商品本身
foreach ($items as $index => $item) {
if ($distributions[$index] > $item['amount']) {
echo "错误:商品{$index}优惠(" . $distributions[$index] . ")超过价格(" . $item['amount'] . ")\n";
return false;
}
}
// 检查3: 不能有负数
foreach ($distributions as $dist) {
if ($dist < 0) {
echo "错误:存在负数优惠\n";
return false;
}
}
return true;
}
// 测试完整示例
$items = [
['id' => 1, 'name' => '商品A', 'amount' => 100],
['id' => 2, 'name' => '商品B', 'amount' => 200],
['id' => 3, 'name' => '商品C', 'amount' => 300],
];
$totalDiscount = 50;
$result = distributeDiscountProportionally($items, $totalDiscount);
if (validateDistribution($items, $result, $totalDiscount)) {
echo "分摊成功!\n";
foreach ($items as $index => $item) {
echo sprintf(
"%s 原价: %.2f 优惠: %.2f 实际支付: %.2f\n",
$item['name'],
$item['amount'],
$result[$index],
$item['amount'] - $result[$index]
);
}
echo "总优惠: " . array_sum($result) . "\n";
}
- 精度处理:始终使用
round()函数处理小数,最后一个项目承担剩余金额 - 边界检查:确保优惠不超过商品价格,不为负数
- 按比例分配:基于商品金额的比例来分配优惠
- 条件判断:处理多个优惠组合时要考虑顺序和优先级
- 验证机制:始终验证分配结果是否准确
这些算法覆盖了大部分电商优惠场景,你可以根据具体业务需求进行调整和扩展。