使用withFilter而不是filter

84

在应用map,flatmap等函数时,始终使用withFilter比filter更高效吗?

为什么只支持map、 flatmap和foreach?(期望像forall / exists这样的函数也被支持)


这部分Scala文档也有详细的解释。 - ohkts11
6个回答

127

来自Scala文档:

注意:c filter pc withFilter p的区别在于前者会创建一个新的集合,而后者仅限制后续mapflatMapforeachwithFilter操作的域。

因此,filter将使用原始集合生成一个新集合,但withFilter将非严格(即惰性地)传递未经过滤的值到以后的map/flatMap/withFilter调用中,从而节省对(过滤后的)集合的第二次遍历。因此,当传递到这些后续方法调用时,它将更有效率。

事实上,withFilter 是专门为处理这些方法链设计的,这也是 for 推导式被解糖成的形式。没有其他方法(如 forall/exists)需要使用,因此它们没有被添加到 withFilterFilterMonadic 返回类型中。

希望有一天他们仍然会添加这些方法。 - Kigyo
1
@Kigyo 我认为你不应该自己使用 withFilter(除了在 for 表达式中隐式使用)。如果你想要映射/过滤器是惰性的,请使用 view - Luigi Plinge
我明白了。viewwithFilter 之间的确切区别是什么?为什么不将 view 用于 for-loops - Kigyo
5
仅供参考,我认为 Collections - Tips and Tricks 提供了非常有价值的信息。该链接中的 H5 标题未被锚定,但您可以在链接的这一部分中搜索“不要创建临时集合”。 - sthzg
4
关于显式使用withFilter,Martin Odersky本人在Coursera的Scala课程中明确地使用了它,我强烈推荐这个课程。鉴于他这么做了,可能会让其他人也更愿意这样做,尽管区别通常只有一个字符。例如,seq.view filter pseq withFilter p - Chuck Daniels
显示剩余4条评论

14

除了Shadowlands的精彩回答之外,我想带来一个直观的例子,来说明filterwithFilter之间的区别。

让我们考虑下面的代码:

val list = List(1, 2, 3)
var go = true
val result = for(i <- list; if(go)) yield {
   go = false
   i
}

大多数人期望result等于List(1)。自Scala 2.8以来就是这种情况,因为for推导式被翻译成了

Most people expect result to be equal to List(1). This is the case since Scala 2.8, because the for-comprehension is translated into

val result = list withFilter {
  case i => go
} map {
  case i => {
    go = false
    i
  }
}

正如您所看到的,该翻译将条件转换为对withFilter的调用。在Scala 2.8之前,for循环推导式会被翻译成以下内容:

val r2 = list filter {
  case i => go
} map {
  case i => {
    go = false
    i
  }
}

如果使用 filter,那么 result 的值会非常不同:List(1, 2, 3)。由于我们将 go 标志设置为 false 并没有对过滤器产生影响,因为过滤器已经完成了操作。同样,在Scala 2.8中,可以使用 withFilter 来解决这个问题。当使用 withFilter 时,在 map 方法内访问每个元素时都会评估该条件。

参考资料: - p.120,Scala in action(覆盖Scala 2.10),Manning Publications,Milanjan Raychaudhuri - Odersky关于for-comprehension翻译的思考


1

forall/exists没有被实现的主要原因是:

  • 您可以将withFilter懒惰地应用于无限流/可迭代对象
  • 您可以懒惰地应用另一个withFilter(一次又一次)

为了实现forall/exists,我们需要获取所有元素,失去了惰性。

例如:

import scala.collection.AbstractIterator

class RandomIntIterator extends AbstractIterator[Int] {
  val rand = new java.util.Random
  def next: Int = rand.nextInt()
  def hasNext: Boolean = true
}

//rand_integers  is an infinite random integers iterator
val rand_integers = new RandomIntIterator

val rand_naturals = 
    rand_integers.withFilter(_ > 0)

val rand_even_naturals = 
    rand_naturals.withFilter(_ % 2 == 0)

println(rand_even_naturals.map(identity).take(10).toList)

//calling a second time we get
//another ten-tuple of random even naturals
println(rand_even_naturals.map(identity).take(10).toList)

请注意,ten_rand_even_naturals仍然是一个迭代器。 只有当我们调用toList时,随机数才会按顺序生成并过滤。
请注意,map(identity)等同于map(i=>i),它在这里用于将一个withFilter对象转换回原始类型(例如集合、流、迭代器)。

1
针对forall/exists部分:
someList.filter(conditionA).forall(conditionB)

这个内容的意思是相同的,尽管有些不太直观。

!someList.exists(conditionA && !conditionB)

同样地,.filter().exists() 可以合并为一个 exists() 检查吗?

-3

使用yield可以是一种解决方法,例如:

for {
  e <- col;
  if e isNotEmpty
} yield e.get(0)

-5

作为一种解决方法,您可以仅使用mapflatMap实现其他功能。

此外,在小集合上进行此优化是无用的...


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接