运行尾递归方法的Stack Overflow

时间:2016-10-14 02:34:06

标签: scala stack-overflow tail-recursion

我测试了Scala中尾递归优化的性能。所以我在eclipse和sbt中测试它。但是,我只得到尾部递归版本比正常情况更糟糕的结果。我想知道它的原因。

这是我的代码。

package MyList

sealed trait List[+A]
case object Nil extends List[Nothing]
case class Cons[+A](head: A, tail: List[A]) extends List[A]

object List { // companion object

  def sum(ints: List[Int]): Int = ints match {
    case Nil => 0
    case Cons(x, xs) => x+sum(xs)
  }

  def sum_tail_recursion(ints: List[Int]): Int = {
    @scala.annotation.tailrec
    def helper(ls: List[Int], res: Int): Int = ls match {
      case Nil => res
      case Cons(x, xs) => helper(xs, res+x)
    }
    helper(ints, 0)
  }

  def generate_tail_recursion(n: Int): List[Int] = {
    @scala.annotation.tailrec
    def helper(x: Int, ls: List[Int]): List[Int] = x match {
      case 0 => ls
      case x => helper(x-1, Cons(x, ls))
    }
    helper(n, Nil)
  }

  def generate(n: Int): List[Int] = n match {
    case 0 => Nil
    case x => Cons(x, generate(x-1))
  }

  def time[A](block: => A): A = {
    val t0 = System.nanoTime()
    val result = block
    val t1 = System.nanoTime()
    println("Elapsed time: " + (t1-t0) + "ns")
    result
  }
}

此外,我发现generate(10000)会导致堆栈溢出但generate_tail_recursion(10000)赢了。 (但后者会导致一些toString错误。我该如何解决?) 那么,如何在Scala中使用尾递归来提高性能呢?谢谢!

更新

这是错误。

当我运行'生成(10000)':

java.lang.StackOverflowError at scala.runtime.BoxesRunTime.boxToInteger(BoxesRunTime.java:70) at MyList.List$.generate(List.scala:56) at MyList.List$.generate(List.scala:56) at MyList.List$.generate(List.scala:56) at MyList.List$.generate(List.scala:56)

当我运行generate_tail_recursion(10000)时:

java.lang.StackOverflowError at scala.collection.AbstractIterator.addString(Iterator.scala:1157) at scala.collection.TraversableOnce$class.mkString(TraversableOnce.scala:286) at scala.collection.AbstractIterator.mkString(Iterator.scala:1157) at scala.runtime.ScalaRunTime$._toString(ScalaRunTime.scala:170) at MyList.Cons.toString(List.scala:5) at java.lang.String.valueOf(Unknown Source) at scala.collection.mutable.StringBuilder.append(StringBuilder.scala:197) at scala.collection.TraversableOnce$$anonfun$addString$1.apply(TraversableOnce.scala:327) at scala.collection.Iterator$class.foreach(Iterator.scala:727) at scala.collection.AbstractIterator.foreach(Iterator.scala:1157) at scala.collection.TraversableOnce$class.addString(TraversableOnce.scala:320) at scala.collection.AbstractIterator.addString(Iterator.scala:1157) at scala.collection.TraversableOnce$class.mkString(TraversableOnce.scala:286) at scala.collection.AbstractIterator.mkString(Iterator.scala:1157) at scala.runtime.ScalaRunTime$._toString(ScalaRunTime.scala:170)

1 个答案:

答案 0 :(得分:1)

这里最意想不到的可能是你似乎从你的方法的尾递归版本中获得了堆栈溢出,所以我将解释为什么会发生这种情况。

简单地说,这是因为你在控制台上运行generate_tail_recursion(10000)。这迫使JVM尝试构建描述整个10,000元素列表的String然后打印它。你可以想象,这将是一个巨大的字符串,因为它看起来像Cons(1,Cons(2,Cons(3,...,Cons(10000,Nil)...)))。您只需运行generate_tail_recursion(10)即可查看此版本的小版本,以便自行确认。这就是堆栈溢出的原因。

为避免立即打印整个列表,您需要在方法体中定义它,例如:

object Main {
  private val Size = 10000

  def main(args: Array[String]): Unit = {
    val list = List time (List generate_tail_recursion Size)
    //val list2 = List time (List generate Size)
  }
}

要清楚了解Scala对@annotation.tailrec的确切了解,请参阅https://stackoverflow.com/a/1682912/20371