Scala 中的简单税收计算

Simple tax calculation in Scala

假设我正在编写一个具有两个功能的玩具税收计算器:

// calculate the tax amount for a particular income given tax brackets
def tax(income: BigDecimal, brackets: Seq[Bracket]): BigDecimal = ???

// calculate the min. income for a particular tax rate given tax brackets 
def income(taxRate: BigDecimal, brackets: Seq[Bracket]) = ???

我这样定义税级:

case class Bracket(maxIncomeOpt: Option[BigDecimal], rate: BigDecimal)

Bracket(Some(BigDecimal(10)), BigDecimal(10)) 表示收入达到 1010% 税级 Bracket(Some(BigDecimal(20)), BigDecimal(20)) 表示收入达到 20
20% 税级 Bracket(None, BigDecimal(30)) 表示任何收入

的税级 30%

现在我正在这样写函数tax

def tax(income: BigDecimal, brackets: Seq[Bracket]): BigDecimal = {
  val (_, result) = brackets.foldLeft((BigDecimal(0), income)) { case ((result, rest), curr) =>
    val taxable = curr.maxIncomeOpt.fold(rest)(_.min(rest))
    (result + taxable * curr.rate / 100.0, rest - taxable)
  }
  result
} 

函数 tax 似乎有效,但认为 Seq[Bracket] 不是定义税级的最佳方式。税收括号是不相交的 "back-to-back" 区间的排序序列,末尾有一个开区间。您如何定义税级?

我会建议大致相同,List[Tuple[Double, Double]] 作为原始形式。

其中元组在语义上 (lower_bound, tax_rate_in_range)。关键区别在于行为的原子单位是税表,而不是单个括号。在此核心数据定义的时间表中,如果数字括号变大,您可以添加优化,并且您可以为原始解决方案保留重要的不变量,例如保持列表按 lower_bound.

排序

我会将税级定义为分段常数函数:

def taxBracket(i: Int): Float = {
  case _ if i < 10 => 0.1
  case _ if i < 20 => 0.2
  case _ => 0.3
}

易于阅读,易于对任何类型的值进行自定义行为(假设它在某处或任何地方变为线性,实际上,您可以根据需要将各个部分绑在一起),并计算金额 N 的税金就是这个函数在 0 和 N 之间的数值积分。

考虑使用代数数据类型定义括号和PositiveInfinity模拟开区间的解决方案

abstract class TaxBracket(val from: Double, val to: Double, val rate: Double) {
  def tax(income: Double) = {
    if (income >= from)
      if (to.isPosInfinity) (income - from) * rate
      else if (income - to > 0) (to - from) * rate
      else (income - (from - 1)) * rate
    else
      0.0
  }
}
case object A extends TaxBracket(0, 12500, 0.0)
case object B extends TaxBracket(12501, 50000, 0.2)
case object C extends TaxBracket(50001, 150000, 0.4)
case object D extends TaxBracket(150001, Double.PositiveInfinity, 0.45)

现在税收计算简化为

def tax(income: Double, bands: List[TaxBracket]): Double =
  bands.map(_.tax(income)).sum

例如,使用上面定义的UK tax bands我们得到

tax(60000, List(A, B, C, D)) // res0: Double = 11499.8

可以验证here.

要获得给定有效税率的最低收入,请尝试

def income(etr: Double, bands: List[TaxBracket]): Option[Double] = {
  bands.map(b => (b.from, b.to)).find { case (from, to) =>
    if (to.isPosInfinity) true
    else (tax(to, bands) / to) >= etr
  }.map { case (lowerBound, upperBound) => lowerBound }
}

income(0.4, List(A, B, C, D)) // res1: Option[Double] = Some(150001.0)