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

时间:2015-06-15 01:08:13

标签: scala dictionary collections 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 }

方法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 }

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

9 个答案:

答案 0 :(得分:7)

可以使用foldRight

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

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()
}

答案 1 :(得分:4)

典型的方法是使用iterator(如果可能)或view(如果iterator无法工作)。这并不完全完全避免两次遍历,但它确实避免了创建一个完整大小的中间集合。然后,您先map然后filter,然后再根据需要再map

xs.iterator.map(x => x*x).filter(_ > N).map(_.toString)

这种方法的优点在于它非常易于阅读,并且由于没有中间集合,因此效率相当高。

如果您因为这是性能瓶颈而要求,那么答案通常是编写尾递归函数或使用旧式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)
}

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

答案 2 :(得分:3)

一种非常简单的方法,只进行一次乘法运算。它也很懒,所以它只在需要时执行代码。

var m=[{"quiz_id":"3","_id":"1","option_in_json":"[{\"option\":\"1\",\"is_answer\":false},{\"option\":\"2\",\"is_answer\":true}]","question":"1+1"}];
alert(m[0].option_in_json);

查看here,了解nums.view.map(x=>x*x).withFilter(x => x> N).map(_.toString) filter之间的差异。

答案 3 :(得分:2)

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

val sqNumsLargerThanN = nums collect {
    case (x: Int) if (x * x) > N => (x * x).toString
}

答案 4 :(得分:2)

我还没有确认这是真正的单程,但是:

  val sqNumsLargerThanN = nums flatMap { x =>
    val square = x * x
    if (square > N) Some(x) else None
  }

答案 5 :(得分:2)

考虑这一点是为了理解,

  for (x <- 0 until 10; v = x*x if v > N) yield v.toString

展开到范围内的flatMap和(懒惰)withFilter到一次只计算的方格,并产生一个带有过滤结果的集合。要注意一个迭代和一个平方的计算是必需的(除了创建范围)。

答案 6 :(得分:0)

您可以使用flatMap

val sqNumsLargerThanN = nums flatMap { x =>
  val square = x * x
  if (square > N) Some(square.toString) else None
}

或者使用Scalaz,

import scalaz.Scalaz._

val sqNumsLargerThanN = nums flatMap { x =>
  val square = x * x
  (square > N).option(square.toString)
}

通过一次迭代解决了如何执行此操作的问题。这在流数据时非常有用,例如Iterator。

但是......如果你想要绝对最快的实现,那就不是了。事实上,我怀疑你会使用一个可变的ArrayList和一个while循环。但只有在剖析后才能确定。在任何情况下,这都是另一个问题。

答案 7 :(得分:0)

使用for comprehension可以工作:

val sqNumsLargerThanN = for {x <- nums if x*x > N } yield (x*x).toString

另外,我不确定,但我认为scala编译器在地图之前对过滤器很聪明,如果可能的话只会进行1次传递。

答案 8 :(得分:-2)

我也是初学者,如下所示

 for(y<-(num.map(x=>x*x)) if y>5 ) { println(y)}