【问题标题】:Stack Overflow on running tail recursive method运行尾递归方法的堆栈溢出
【发布时间】:2016-10-14 02:34:05
【问题描述】:

我在 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)

【问题讨论】:

  • 你能发布确切的错误吗?
  • @Yawar 我更新了它。谢谢!!
  • 当你说'尾递归版本的工作比正常的差很多'时,你的意思是运行时速度更差,还是你得到这个错误?
  • @Yawar 我的意思是运行速度。理论上,尾递归版本应该比正常的快吧?

标签: scala stack-overflow tail-recursion


【解决方案1】:

可能最出乎意料的是,您的方法的尾递归版本似乎出现了堆栈溢出,所以我将解释为什么会发生这种情况。

简单地说,这是因为您在控制台上运行generate_tail_recursion(10000)。这迫使 JVM 尝试构建一个 String 描述整个 10,000 元素列表,然后打印它。可以想象,这将是一个巨大的字符串,因为它看起来像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

【讨论】:

  • 我知道了。但是在REPL中,当我生成一个List时,它会自动打印出来,我也没有找到任何处理的方法。所以我将无法在 REPL 中生成我的列表。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-08-26
  • 1970-01-01
  • 2020-07-13
  • 2012-11-06
  • 1970-01-01
  • 2020-03-06
  • 1970-01-01
相关资源
最近更新 更多