Scala:过滤和维护的最佳方式;一次迭代映射
我是Scala新手,正在尝试找出过滤和映射集合的最佳方法。这里有一个玩具的例子来解释我的问题 方法1:这非常糟糕,因为我在列表中迭代了两次,每次迭代都计算相同的值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
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
时,如果列表是