Простой расчет налога в Scala - PullRequest
2 голосов
/ 21 января 2020

Предположим, я пишу игрушечный налоговый калькулятор с двумя функциями:

// 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)) означает налоговую скобку 10% для дохода выше tp 10 Bracket(Some(BigDecimal(20)), BigDecimal(20)) означает ставку налога 20% для дохода выше tp 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] - не лучший способ определения налоговых скобок. Налоговые скобки представляют собой отсортированную последовательность непересекающихся интервалов «спина к спине» с открытым интервалом в конце. Как бы вы определили налоговые скобки?

Ответы [ 3 ]

1 голос
/ 21 января 2020

Я бы определил налоговые скобки как кусочно-постоянную функцию:

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

Легко читать, легко настраивать поведение для любого типа значения (скажем, оно становится линейным где-то или чем-то на самом деле, Вы можете просто t ie штук, сколько хотите), а расчет налога для суммы N представляет собой просто числовую интеграцию этой функции между 0 и N.

1 голос
/ 21 января 2020

Рассмотрим решение с использованием алгебраических c типов данных для определения скобок и 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

, например, с использованием Налоговые полосы Великобритании , определенные выше, мы получаем

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

, который можно проверить здесь .

Чтобы получить минимальный доход для данной эффективной ставки налога попробуйте

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)
1 голос
/ 21 января 2020

Я бы предложил примерно то же самое, List[Tuple[Double, Double]] как необработанную форму.

Где семантически кортеж (lower_bound, tax_rate_in_range). Ключевым отличием является то, что единицей поведения атома c является налоговая таблица, а не отдельные скобки. В графике, определяемом этими основными данными, вы можете добавить оптимизации, если числовые скобки станут большими, и вы можете сохранить важный инвариант для наивного решения, такого как сохранение списка, упорядоченного по lower_bound.

...