我一直在写一本关于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)
}
这种扭转结果以恢复原始顺序的模式是我关心的问题。是否有更好的方法(没有reverse
调用)在FP中,特别是在Scala中执行此操作?
答案 0 :(得分:5)
您可以尝试使用Vector等数据结构,它将在有效的恒定时间附加值。
所以你在哪里:
case Cons(h, t) => go(t(), h() :: acc)
改为使用:
case Cons(h,t) => go(t(), acc :+ h())
当你返回累加器时,不需要反转。
答案 1 :(得分:3)
嗯,有各种各样的方法,即使没有转向非尾部,也不应该超过可能大的容器的大小。
首先你应该问:你为什么关心?如果您的代码足够快,为什么要避免反转?
然后最简单的解决方案可能是:在本地使用可变构建器。有一个ListBuffer,用于通过逐步追加元素来构建列表。由于性能原因,Scala API中的实现会大量使用它。
如果您不想直接使用列表缓冲区,则可以进行延迟计算并将其转换为列表。在某些情况下,这可能比使用累加器更具可读性,但它可能不会更有效。
> Sample.Frame
Start.Date LON.NEW6 LON.CAP LON.CEN2 LON.MYH1 LON.SOS LON.MOW LON.HOLW LON.94V LON.FOU6 LON.949
E0-001-085571068-9 30/09/2015 3 NA NA NA NA NA NA NA NA NA
E0-001-086838711-7 07/11/2015 1 NA NA NA NA NA NA NA NA NA
E0-001-085536178-4@2015102019 20/10/2015 NA NA NA NA NA NA NA NA NA NA
E0-001-085466318-0 01/07/2016 NA NA NA NA NA NA NA NA NA NA
E0-001-085591039-5 30/01/2016 NA NA NA NA NA NA NA NA NA NA
E0-001-087500856-4 29/04/2016 NA NA NA NA NA NA NA NA NA NA
E0-001-079398784-2@2015092909 29/09/2015 NA NA NA NA NA NA NA NA NA NA
E0-001-086021337-5 14/10/2015 NA NA NA NA NA NA NA NA NA NA
E0-001-086639435-3 20/12/2015 NA NA NA NA NA NA NA NA NA NA
E0-001-087220018-9 27/10/2015 NA NA NA NA NA NA NA NA NA NA
答案 2 :(得分:3)
如果你想保持尾递归,你应该使用Difference List作为累加器:它们基本上是一个带有指向Nil
构造函数的直接指针的列表,它允许你在常量中进行连接时间。
答案 3 :(得分:-4)
根本不使用累加器
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)
}
如评论中所述,此解决方案不再是尾递归。是什么累积器主要用于。