【问题标题】:How to proportionally distribute discount on order lines?如何在订单行上按比例分配折扣?
【发布时间】:2019-07-09 15:01:55
【问题描述】:

我有一个订单,其中包含行数和折扣,需要在这些行之间按行成本按比例分配。

我不是数学家,所以我会介绍这个符号来解释这个案例。订单有N 行,商品价格为Pi,商品数量为Qi,总行成本为Ti,其中Ti = Qi * Pi。总订单价格为T = sum(Ti)。算法需要分配折扣D,结果是Di 的列表 - 为每个订单行分配折扣。 结果必须满足以下条件:

  • D = sum(Di):线路折扣之和必须等于原始折扣
  • Di%Qi = 0:折扣必须能被数量整除,没有余数
  • Di <= Ti:折扣不能超过总线路成本
    • Di/D ~ Ti/T:折扣尽可能按比例分配

输入数据满足以下谓词:

  • D <= T,折扣不超过总订单成本
  • DDiQi 是整数值,Pi 是十进制值
  • 输入数据的变化不能满足要求的条件。例如,3 行,每行有 3 个价格为 10 的商品,输入折扣为 10 (N=3; Qi=3; Pi=10; D=10)。无法将其分配为可除以行数。在这种情况下,算法应该返回错误,其中包含无法分配的折扣量(对于我的示例,它是 1)

现在我们的算法实现是这样的(F#上的简化版)

type Line = {
  LineId: string
  Price: decimal
  Quantity: int
  TotalPrice: decimal
  Discount: decimal
}

module Line =
  let minimumDiscount line =
    line.Quantity
    |> decimal
    |> Some
    |> Option.filter (fun discount -> discount <= line.TotalPrice - line.Discount)

  let discountedPerItemPrice line = line.Price - line.Discount / (decimal line.Quantity)

let spread discount (lines: Line list) =
  let orderPrice = lines |> List.sumBy (fun l -> l.TotalPrice)
  let preDiscountedLines = lines |> List.map (fun line ->
    let rawDiscount = line.TotalPrice / orderPrice * discount
    let preDiscount = rawDiscount - rawDiscount % (decimal line.Quantity)
    {line with Discount = preDiscount})

  let residue = discount - List.sumBy (fun line -> line.Discount) preDiscountedLines

  let rec spreadResidue originalResidue discountedLines remainResidue remainLines =
    match remainLines with
    | [] when remainResidue = 0m -> discountedLines |> List.rev |> Ok
    | [] when remainResidue = originalResidue -> sprintf "%f left to spread" remainResidue |> Error
    | [] -> discountedLines |> List.rev |> spreadResidue remainResidue [] remainResidue
    | head :: tail ->
      let minimumDiscountForLine = Line.minimumDiscount head
      let lineDiscount = minimumDiscountForLine
                           |> Option.filter (fun discount -> discount <= remainResidue)
                           |> Option.defaultValue 0m
      let discountedLine = {head with Discount = head.Discount + lineDiscount}
      let discountedLines = discountedLine :: discountedLines
      let remainResidue = remainResidue - lineDiscount
      spreadResidue originalResidue discountedLines remainResidue tail

  spreadResidue residue [] residue preDiscountedLines

该算法采用了here 中的一些解决方案,适用于大多数情况。 但是,它在以下情况下失败:

P1=14.0; Q1=2;
P2=11.0; Q2=3;
D=52

至少存在一种可能的分布:D1=22; D2=30,但当前算法无法发现它。那么什么是更好的传播算法或更好的传播残差算法?

【问题讨论】:

    标签: algorithm math f#


    【解决方案1】:

    让我们将Di/D ~ Ti/T 解释为Di ∈ {Qi*floor(D*Pi/T), Qi*ceiling(D*Pi/T)}。然后我们可以将这个问题作为子集和来解决,即对于每个i 使得D*Ti/T/Qi 不是整数并且对于其中Qi*ceiling(D*Pi/T) ≤ Pi,我们有一个权重项Q_i,目标和是D - sum_i Q_i*floor(D*Pi/T)。子集和是 NP 难的,但只是微弱的,除非你有巨大的数量,否则使用传统的动态程序解决它应该没有问题。如果问题不可行,您可以使用动态程序中的最终表格来找出最佳余数。

    作为扩展,您可能希望采用以下解决方案:虽然D*Ti/T 不是整数,但Di 比替代方案更接近该比率。您可以定义像|floor(D*Ti/T/Qi) - D*Ti/T|^2 - |ceiling(D*Ti/T/Qi) - D*Ti/T|^2 这样的项目利润,这些利润有利于最优折扣更接近上限而不是下限的项目。然后你正在解决一个背包问题(嗯,有点,因为“利润”可能是负数)而不是子集总和,但 DP 变化不大。

    【讨论】:

      猜你喜欢
      • 2017-04-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-10-06
      • 2022-06-30
      • 2019-08-28
      相关资源
      最近更新 更多