Scala 我发现自己在大多数功能结束时都会反转累加器;我怎么能停下来?
我一直在读一本关于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
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)。