8

我是 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 }

那么,是否可以在不迭代集合两次并避免重复相同计算的情况下计算它?

4

9 回答 9

7

可以使用一个foldRight

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

AfoldLeft也将实现类似的目标,但结果列表将是相反的顺序(由于foldLeft.

或者,如果您想玩 Scalaz

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

nums.foldMap { i =>
  val s = i * i
  if (s > N) List(s) else List()
}
于 2015-06-15T01:31:47.340 回答
5

典型的方法是使用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而不是使用外部构建器(对求和特别有用)。

于 2015-06-15T01:29:47.510 回答
3

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

val sqNumsLargerThanN = nums collect {
    case (x: Int) if (x * x) > N => (x * x).toString
}
于 2015-06-15T01:17:09.877 回答
3

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

nums.view.map(x=>x*x).withFilter(x => x> N).map(_.toString)

在此处查看filter和之间的差异withFilter

于 2015-06-15T01:33:50.613 回答
3

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

  val sqNumsLargerThanN = nums flatMap { x =>
    val square = x * x
    if (square > N) Some(x) else None
  }
于 2015-06-15T01:37:46.227 回答
2

考虑到这一点,以便理解,

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

展开flatMap范围内的 a 和 (lazy)withFilter到曾经唯一计算过的正方形上,并产生一个带有过滤结果的集合。请注意,需要进行一次迭代和一次平方计算(除了创建范围之外)。

于 2015-06-15T04:20:15.210 回答
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)
}

解决了如何通过一次迭代来做到这一点的问题。这在流式传输数据时很有用,例如使用迭代器。

但是...如果您想要绝对最快的实现,那不是。事实上,我怀疑你会使用一个可变的 ArrayList 和一个 while 循环。但只有在分析之后,你才能确定。无论如何,这是另一个问题。

于 2015-06-15T06:09:59.667 回答
0

使用 for 理解会起作用:

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

另外,我不确定,但我认为 scala 编译器对映射前的过滤器很聪明,如果可能的话只会做 1 次。

于 2015-06-15T11:31:57.793 回答
-2

我也是初学者这样做如下

 for(y<-(num.map(x=>x*x)) if y>5 ) { println(y)}
于 2016-03-14T14:51:04.280 回答