Scala:在一次迭代中过滤和映射的最佳方式

Can*_*Bal 7 collections dictionary scala filter collect

我是Scala的新手,并试图找出过滤和映射集合的最佳方法.这是一个解释我的问题的玩具示例.

方法1:这非常糟糕,因为我在列表中迭代两次并在每次迭代中计算相同的值.

val N = 5
val nums = 0 until 10
val sqNumsLargerThanN = nums filter { x: Int => (x * x) > N } map { x: Int => (x * x).toString }
Run Code Online (Sandbox Code Playgroud)

方法2:这稍微好一点,但我还需要计算(x * x)两次.

val N = 5
val nums = 0 until 10
val sqNumsLargerThanN = nums collect { case x: Int if (x * x) > N => (x * x).toString }
Run Code Online (Sandbox Code Playgroud)

那么,是否可以计算这个而不重复两次收集并避免重复相同的计算?

ade*_*rtc 7

可以用一个 foldRight

nums.foldRight(List.empty[Int]) {
  case (i, is) =>
    val s = i * i
    if (s > N) s :: is else is
  }
Run Code Online (Sandbox Code Playgroud)

A foldLeft也会实现类似的目标,但结果列表的顺序是相反的(由于结果的相关性)foldLeft.

或者,如果你想和Scalaz一起玩

import scalaz.std.list._
import scalaz.syntax.foldable._

nums.foldMap { i =>
  val s = i * i
  if (s > N) List(s) else List()
}
Run Code Online (Sandbox Code Playgroud)


Rex*_*err 5

典型的方法是使用iterator(如果可能)或view(如果iterator不起作用)。这并不能完全避免两次遍历,但它确实避免了创建全尺寸的中间集合。然后map,您首先和filter之后,然后map根据需要再次:

xs.iterator.map(x => x*x).filter(_ > N).map(_.toString)
Run Code Online (Sandbox Code Playgroud)

这种方法的优点是它真的很容易阅读,而且由于没有中间集合,所以它的效率相当高。

如果你问是因为这是一个性能瓶颈,那么答案通常是写一个尾递归函数或使用老式的 while 循环方法。例如,在你的情况下

def sumSqBigN(xs: Array[Int], N: Int): Array[String] = {
  val ysb = Array.newBuilder[String]
  def inner(start: Int): Array[String] = {
    if (start >= xs.length) ysb.result
    else {
      val sq = xs(start) * xs(start)
      if (sq > N) ysb += sq.toString
      inner(start + 1)
    }
  }
  inner(0)
}
Run Code Online (Sandbox Code Playgroud)

您还可以向前传递一个参数,inner而不是使用外部构建器(对于求和尤其有用)。


Gav*_*ulz 2

您可以使用collect,它将部分函数应用于定义它的集合的每个值。您的示例可以重写如下:

val sqNumsLargerThanN = nums collect {
    case (x: Int) if (x * x) > N => (x * x).toString
}
Run Code Online (Sandbox Code Playgroud)