Scala - 折叠由对象交互产生的值

时间:2012-02-17 09:36:15

标签: scala folding

在Scala中,我有一个表示点的对象列表,包含xy值。该列表描述了顺序遍历所有这些点的路径。我的问题是如何在该列表上使用折叠以找到路径的总长度?或者甚至可能有更好的功能或Scala方式来做到这一点?

我想出的是:

def distance = (0 /: wps)(Waypoint.distance(_, _)) 

但是当然这是完全错误的,因为distance会返回Float,但会接受两个Waypoint个对象。

更新

感谢您提出的解决方案!它们肯定很有趣,但我认为这对于可能变得很重的实时计算来说太功能了。到目前为止,我已经提出了这些问题:

val distances = for(i <- 0 until wps.size) yield wps(i).distanceTo(wps(i + 1))
val distance = (0f /: distances)(_ + _)

我认为这是一个公平的命令/功能组合既快速又留下每个航点之间的距离值以进一步可能的参考,这对我来说也是一个好处。

更新2:实际上,为了确定什么是更快,我将不得不对所有类型的序列的所有建议解决方案进行基准测试。

4 个答案:

答案 0 :(得分:4)

这应该有用。

(wps, wps drop 1).zipped.map(Waypoint.distance).sum

答案 1 :(得分:3)

如果可以在此处使用折叠,请不要know,但请尝试以下操作:

wps.sliding(2).map(segment => Waypoint.distance(segment(0), segment(1))).sum

wps.sliding(2)返回所有后续对的列表。或者如果您更喜欢模式匹配:

wps.sliding(2).collect{case start :: end :: Nil => Waypoint.distance(start, end)}.sum

BTW考虑定义:

def distanceTo(to: Waypoint)
直接在Waypoint类上的

,而不是在伴随对象上,因为它看起来更像面向对象,并且允许你编写类似DSL的代码:

point1.distanceTo(point2)

甚至:

point1 distanceTo point2

wps.sliding(2).collect{
  case start :: end :: Nil => start distanceTo end
}.sum

答案 2 :(得分:1)

您的评论“对于可能变得很重的实时计算而言功能太多”会让这很有趣。基准测试和分析是至关重要的,因为您不想为了性能而编写一堆难以维护的代码,只是为了发现它首先不是您应用程序的性能关键部分!或者,更糟糕的是,发现您的性能优化会使您的特定工作负载变得更糟。

性能最佳的实现将取决于您的具体情况(路径有多长?系统中有多少核心?)但我认为混合命令式和功能性方法可能会给您带来最糟糕的两个世界。如果你不小心,你可能会失去可读性和性能!

我会稍微修改missingfaktor's answer,以便让您从parallel collections获得性能提升。简单地添加.par可以为您带来巨大的性能提升,这一事实证明了坚持功能性编程的强大功能!

def distancePar(wps: collection.GenSeq[Waypoint]): Double = {
  val parwps = wps.par
  parwps.zip(parwps drop 1).map(Function.tupled(distance)).sum
}

我的猜测是,如果您有多个内核可以解决问题,这将最有效,wps往往有点长。如果你有很少的核心或短路径,那么并行性可能会比它有所帮助更多。

另一个极端将是一个完全必要的解决方案。只要避免共享的可变状态,编写单个的,性能关键的函数的命令式实现通常是可以接受的。但是一旦你习惯了FP,你会发现这种函数更难以编写和维护。并行化并不容易。

def distanceImp(wps: collection.GenSeq[Waypoint]): Double = {
  if (wps.size <= 1) {
    0.0
  } else {
    var r = 0.0
    var here = wps.head
    var remaining = wps.tail

    while (!remaining.isEmpty) {
      r += distance(here, remaining.head)
      here = remaining.head
      remaining = remaining.tail
    }
    r
  }
}

最后,如果你正在寻找FP和命令之间的中间地带,你可能会尝试递归。我没有对它进行分析,但我的猜测是,这将大致相当于在性能方面的必要解决方案。

def distanceRec(wps: collection.GenSeq[Waypoint]): Double = {
  @annotation.tailrec
  def helper(acc: Double, here: Waypoint, remaining: collection.GenSeq[Waypoint]): Double =
    if (remaining.isEmpty)
      acc
    else
      helper(acc + distance(here, remaining.head), remaining.head, remaining.tail)

  if (wps.size <= 1)
    0.0
  else
    helper(0.0, wps.head, wps.tail)
}

答案 3 :(得分:1)

如果您要使用Vector进行任何类型的索引编制,而不是List:

scala> def timed(op: => Unit) = { val start = System.nanoTime; op; (System.nanoTime - start) / 1e9 }
timed: (op: => Unit)Double

scala> val l = List.fill(100000)(1)
scala> val v = Vector.fill(100000)(1)


scala> timed { var t = 0; for (i <- 0 until l.length - 1) yield t += l(i) + l(i + 1) }
res2: Double = 16.252194583

scala> timed { var t = 0; for (i <- 0 until v.length - 1) yield t += v(i) + v(i + 1) }
res3: Double = 0.047047654

ListBuffer提供快速附加功能,不提供快速随机访问。