Scala 我发现自己在大多数功能结束时都会反转累加器;我怎么能停下来?

Scala 我发现自己在大多数功能结束时都会反转累加器;我怎么能停下来?,scala,functional-programming,Scala,Functional Programming,我一直在读一本关于Scala函数式编程的书(书名就是这样)。通过练习,我发现自己经常在用累加器收集最终结果时颠倒。我记得在我的球拍生涯中也有类似的模式。我担心的是,我可能会使我的代码稍微凌乱一些,可能会执行额外的O(n)操作(其中n是累加器/结果的长度) 例如: // Produces a List from a Stream, forcing evaluation of all elements. def toList(): List[A] = { def go(l: Stream[A

我一直在读一本关于Scala函数式编程的书(书名就是这样)。通过练习,我发现自己经常在用累加器收集最终结果时颠倒。我记得在我的球拍生涯中也有类似的模式。我担心的是,我可能会使我的代码稍微凌乱一些,可能会执行额外的
O(n)
操作(其中
n
是累加器/结果的长度)

例如:

// Produces a List from a Stream, forcing evaluation of all elements.
def toList(): List[A] = {
    def go(l: Stream[A], acc: List[A]): List[A] = {
        l match {
            case Empty if acc.nonEmpty => acc.reverse
            case Empty if acc.isEmpty => Nil
            case Cons(h, t) => go(t(), h() :: acc)
        }
    }
    // "this" is a Stream[A].
    go(this, Nil)
}

我关心的是这种扭转结果以恢复原始秩序的模式。在FP中,特别是在Scala中,是否有更好的方法(无
反向
调用)来实现这一点?

您可以尝试使用一种数据结构,例如Vector,它将在有效的恒定时间内附加一个值

因此,你有:

case Cons(h, t) => go(t(), h() :: acc)
改用:

case Cons(h,t) => go(t(), acc :+ h())

然后,当您返回累加器时,不需要反转。

只是不要使用累加器

def toList(): List[A] = {
    def go(l: Stream[A]): List[A] = {
        l match {
            case Empty  => Nil
            case Cons(h, t) => h :: go(t)
        }
    }
    go(this)
}

如注释中所述,此解决方案不再是尾部递归的。是累加器的主要用途。

好吧,有多种方法,即使不使用非尾递归,也不应该在可能较大的容器大小上使用

首先你应该问:你为什么在乎?如果您的代码足够快,为什么要避免反转

那么最简单的解决方案可能是:在本地使用可变构建器。有一个ListBuffer正是为了通过一步一步地追加元素来构建列表。scalaapi中的实现出于性能考虑,经常使用这一点

如果不想直接使用列表缓冲区,可以进行延迟计算并将其转换为列表。在某些情况下,这可能比使用累加器更具可读性,但可能不会更有效

def map[A,B](list: List[A], f: A => B): Stream[B] = list match {
  case x :: xs  => Stream(f(x)) #:: map(xs, f)
  case Nil => Stream() 
}
map(list, (x: Int) => x + 1).toList

如果你想保持尾部递归,你应该使用a作为累加器:它们基本上是一个列表,有一个指向
Nil
构造函数的直接指针,它允许你在恒定时间内进行串联。

Ah,所以你是说,给定我的数据类型,我的反向调用是不可避免的,对于类似单链表的数据结构,这将是正确的做法。您还说Scala的
Vector
数据类型(可能还有其他数据类型)如果使用得当,可以避免这种开销,对吗?问题源于您在构建列表时预先添加了列表。因此,您添加的第一个元素将位于列表的末尾。在许多情况下,如您的情况,这将是“反向”顺序。因此,要解决这个问题,只需附加这些项即可。向量的作用是,使用向量(或堆栈或队列)执行追加函数。但是对于一个列表,它不是。看看更多的信息现在你有了O(n^2)而不是O(n)@talex-No,append和prepend都是带有向量的O(1)。(有点。你也可以为O(logn)辩护,但基数很大。常数项也是如此。)无论如何,这个运算肯定不是O(n^2)。这就是这个答案的全部要点--了解你的数据结构。啊,这太简单了。非常感谢。然而,我好奇的不是尾巴。是否可以实现尾部递归
reverse
基本上,这是将调用堆栈用作累加器,并将堆栈下的链式返回用作自动
reverse
…这对于玩具示例非常有用。这对于真正的代码来说是可怕的,因为它在非小列表上抛出堆栈溢出异常。因此,这不是一个好的做法。对于
列表来说,这是两个弊病中较小的一个。前置+反向是O(n),而追加则是O(n^2)。