Scala:过滤和维护的最佳方式;一次迭代映射

Scala:过滤和维护的最佳方式;一次迭代映射,scala,dictionary,collections,filter,collect,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)两次 va

我是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 }
那么,是否可以在不迭代集合两次的情况下计算此值,并避免重复相同的计算?

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

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

典型的方法是使用
迭代器
(如果可能)或
视图
(如果
迭代器
不起作用)。这并不能完全避免两次遍历,但可以避免创建一个完整大小的中间集合。然后,您可以先
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)
}

您还可以在
内部
中向前传递参数,而不使用外部生成器(特别适用于求和)。

可以使用
文件夹

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

这是一种非常简单的方法,只执行一次乘法运算。它也是惰性的,因此它只在需要时执行代码

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

查看
filter
withFilter

之间的差异,我还没有确认这确实是一个单一过程,但是:

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

考虑一下这一点以便理解

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


它在整个范围内展开为一个
flatMap
,在只计算一次的平方上展开一个(lazy)
with filter
,并生成一个带有过滤结果的集合。要注意,除了创建范围外,还需要一次迭代和一次平方计算。

您可以使用
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)
}

解决了如何通过一次迭代来实现这一点的问题。这在流式传输数据时非常有用,比如使用迭代器


然而,如果您想要的是绝对最快的实现,则不是这样。事实上,我怀疑您会使用可变数组列表和while循环。但只有在分析之后,您才能确定。在任何情况下,这是另一个问题。

使用用于理解的

val sqNumsLargerThanN = for {x <- nums if x*x > N } yield (x*x).toString
val sqNumsLargerThanN=对于{xn}收益率(x*x).toString

另外,我不确定,但我认为scala编译器在映射之前的过滤器方面很聪明,如果可能的话,它只会执行一次传递。

我也是初学者,它是这样做的

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

我想问一下,为选项层包装每个元素的加载是否比计算x*x两次要轻?选项对象创建成本是否可以忽略?(我是C++的Scala新手)直接回答你的问题,不,选项分配不是免费的。不过很便宜。多年来,JVMGC在分配和收集循环中的小对象方面做得非常好。因此,虽然不是免费的,但这几乎从来都不是我开始优化的地方。此外,我应该指出,虽然这是一个有趣的难题,但在函数编程世界中,尽量减少集合的传递次数通常不是获得性能的最佳方法。这些东西在C/C++世界中很常见,在JVM中则不常见。话虽如此,让我们假设您的收藏量很大,比如8GB。那么你真的只想通过一次,我会坚持使用collect,或者使用惰性集合。双倍乘法将通过JIT进行优化。这是一个单一的过程,但这有点欺骗,因为你创建了第二个集合的东西作为选项。(甚至还有第三种情况,因为如果不隐式转换为
Iterable
,就不能
flatMap
它),所以它通常比
迭代器更重,即使从某种意义上说,它在某种程度上是单次通过,而迭代器却不是。是的,它确实感觉像作弊:)为什么有人投票否决了这个答案
collect
似乎是一种非常惯用的方法。这不是与我的“方法2”完全相同吗?是的,它与上面的方法2相同,根据collect的定义,这一方法对我来说似乎完全合理;它说的正是它所做的。这并不是说上面阐述的其他方法更好或更差。嗨,雷克斯-你说它不完全避免两次遍历是什么意思?@sourcedelica-每个迭代器在遍历列表时,也(必然)遍历前面的迭代器。所以它们都是在锁步中遍历的,但是如果你映射,然后过滤,然后映射,你实际上有嵌套三个深度的next/hasNext调用。这非常有趣。在您链接到的线程中,有一条注释“我认为您不应该自己使用withFilter(除了在for表达式中隐式使用)。是否有理由不使用
with filter
只有当我想创建一个新的集合以供以后使用时,我才使用
filter
。如果我只想将筛选器作为操作管道的中间步骤,我总是使用
with filter
。请注意,使用默认的
foldRight
时,如果列表是