Scala tail recursive method has an divide and remainder error

旧街凉风 提交于 2021-02-19 07:47:28

问题


I'm currently computing the binomial coefficient of two natural numbers by write a tail recursion in Scala. But my code has something wrong with the dividing numbers, integer division by k like I did as that will give you a non-zero remainder and hence introduce rounding errors. So could anyone help me figure it out, how to fix it ?

 def binom(n: Int, k: Int): Int = {
    require(0 <= k && k <= n)
    def binomtail(n: Int, k: Int, ac: Int): Int = {
      if (n == k || k == 0) ac
      else binomtail(n - 1, k - 1, (n*ac)/k)
    }
    binomtail(n,k,1)
  }

回答1:


In general, it holds:

binom(n, k) = if (k == 0 || k == n) 1 else binom(n - 1, k - 1) * n / k

If you want to compute it in linear time, then you have to make sure that each intermediate result is an integer. Now,

binom(n - k + 1, 1)

is certainly an integer (it's just n - k + 1). Starting with this number, and incrementing both arguments by one, you can arrive at binom(n, k) with the following intermediate steps:

binom(n - k + 1, 1)
binom(n - k + 2, 2)
...
binom(n - 2, k - 2)
binom(n - 1, k - 1)
binom(n, k)

It means that you have to "accumulate" in the right order, from 1 up to k, not from k down to 1 - then it is guaranteed that all intermediate results correspond to actual binomial coefficients, and are therefore integers (not fractions). Here is what it looks like as tail-recursive function:

def binom(n: Int, k: Int): Int = {
  require(0 <= k && k <= n)
  @annotation.tailrec 
  def binomtail(nIter: Int, kIter: Int, ac: Int): Int = {
    if (kIter > k) ac
    else binomtail(nIter + 1, kIter + 1, (nIter * ac) / kIter)
  }
  if (k == 0 || k == n) 1
  else binomtail(n - k + 1, 1, 1)
}

Little visual test:

val n = 12
for (i <- 0 to n) {
  print(" " * ((n - i) * 2))
  for (j <- 0 to i) {
    printf(" %3d", binom(i, j))
  }
  println()
}

prints:

                           1
                         1   1
                       1   2   1
                     1   3   3   1
                   1   4   6   4   1
                 1   5  10  10   5   1
               1   6  15  20  15   6   1
             1   7  21  35  35  21   7   1
           1   8  28  56  70  56  28   8   1
         1   9  36  84 126 126  84  36   9   1
       1  10  45 120 210 252 210 120  45  10   1
     1  11  55 165 330 462 462 330 165  55  11   1
   1  12  66 220 495 792 924 792 495 220  66  12   1

Looks ok, compare it with this, if you want.




回答2:


Andrey Tyukin's excellent example will fail with larger n, say binom(10000, 2), but can be easily adapted to use BigInt.

def binom(n: Int, k: Int): BigInt = {
  require(0 <= k && k <= n)
  @annotation.tailrec 
  def binomtail(nIter: Int, kIter: Int, ac: BigInt): BigInt = {
    if (kIter > k) ac
    else binomtail(nIter + 1, kIter + 1, (nIter * ac) / kIter)
  }
  if (k == 0 || k == n) 1
  else binomtail(n - k + 1, 1, BigInt(1))
}


来源:https://stackoverflow.com/questions/52795217/scala-tail-recursive-method-has-an-divide-and-remainder-error

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!