【问题标题】:List foldRight Always Using foldLeft?列出 foldRight 总是使用 foldLeft?
【发布时间】:2013-10-23 17:05:22
【问题描述】:

我刚刚看了List.scalafoldRight()的实现。

  override def reverse: List[A] = {
    var result: List[A] = Nil
    var these = this
    while (!these.isEmpty) {
      result = these.head :: result
      these = these.tail
    }
    result
  }

  override def foldRight[B](z: B)(op: (A, B) => B): B =
    reverse.foldLeft(z)((right, left) => op(left, right))

据我了解,在List 上调用foldRight 会导致调用theList.reverse.foldLeft(...)

是否使用foldLeft 实现List.foldRight 以便利用单个堆栈帧而不是使用多个带有foldLeft 的堆栈帧?

【问题讨论】:

标签: scala


【解决方案1】:

foldLeft 是尾递归的,reverse 根本不是递归的:这种实现确保了恒定的内存使用。 foldRight,当没有按照 foldLeft 实现时,不是尾递归的,这对于大量数据来说是不安全的。

注意:可能有一些方法可以使 foldRight 尾递归,但我能想到的所有方法都需要在列表末尾追加内容,这意味着要完整地遍历它。如果你还是要这样做,最好使用foldLeft 并反转结果,它涉及的整个列表中的完整迭代要少得多。

【讨论】:

  • 虽然当前版本是尾递归的,但反转列表的成本会累积...因此,如果您连续多次这样做,请使用 foldLeft 并反转列表最后......或者更好的是,尝试最适合您要求的集合,例如Vector
  • [修复以前删除的评论] 只是为了补充 Nicolas Rinaudo 的回答,while 循环很快!功能性 + 不可变的习惯用法倾向于更好地扩展。命令式 + 可变风格往往具有更好的原始性能。所以反向需要线性时间,但它实际上很快。 foldRight 已增强为用户 reverse + foldRight,以前它不是尾递归(参见 v2.0 List.scala)。无论如何,线性时间就是线性时间,所以,只要有可能,请遵循上述建议。
  • 有趣的是,Scalaz 以foldRight 的形式实现了Foldable#foldLeftM
  • makes it unsafe for large amounts of data. 这仅适用于存储在堆栈上的数据?正如我在这里所理解的(stackoverflow.com/questions/22593362/…),由于在堆上严格评估的数据,使用带有 Stream 的尾递归可能会导致 OutOfMemory 异常。
  • 从技术上讲,您不应该在尾递归调用中使用 Stream 作为累加器 - 在某种程度上,Stream 是它自己的累加器。除此之外,非尾递归函数是不安全的,因为对自身的每次进一步调用都会导致一个新的堆栈帧,如果你足够“深入”,最终将导致StackOverflowError。尾递归函数经过优化,可以在单个堆栈帧中执行(本质上,它变成了while 循环),无论要处理的数据量多么大,都不会导致堆栈溢出。
猜你喜欢
  • 2013-06-12
  • 1970-01-01
  • 1970-01-01
  • 2011-09-09
  • 2017-03-25
  • 2017-11-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多