Scala:有没有理由更喜欢`filter + map`而不是`collect`?

时间:2016-05-01 00:38:58

标签: scala

有没有理由更喜欢filter+map

list.filter (i => aCondition(i)).map(i => fun(i))

超过collect? :

list.collect(case i if aCondition(i) => fun(i))

collect(单一外观)的那个看起来更快更干净。所以我总是去collect

3 个答案:

答案 0 :(得分:39)

Scala的大多数集合急切地应用操作,并且(除非您使用的宏程序库为您执行此操作)不会融合操作。因此filter后跟map通常会创建两个集合(即使您使用Iterator或某些集合,也会暂时创建中间表单,尽管只是一次一个元素),而collect不会。

另一方面,collect使用部分函数来实现联合测试,部分函数在测试集合中是否存在某些内容时比谓词(A => Boolean)慢。

此外,有些情况下,读取其中一个比另一个更清晰,并且您不关心性能或内存使用差异大约2倍左右。在这种情况下,使用更清楚的。通常,如果您已经拥有了名为的函数,则可以更清楚地阅读

xs.filter(p).map(f)
xs.collect{ case x if p(x) => f(x) }

但如果您提供内联闭包,collect通常看起来更干净

xs.filter(x < foo(x, x)).map(x => bar(x, x))
xs.collect{ case x if foo(x, x) => bar(x, x) }

即使它不一定更短,因为你只引用变量一次。

现在,性能差异有多大?这有所不同,但如果我们考虑这样的集合:

val v = Vector.tabulate(10000)(i => ((i%100).toString, (i%7).toString))

并且你想根据过滤第一个条目来选择第二个条目(因此过滤器和地图操作都非常简单),然后我们得到下表。

注意:可以在集合中获取惰性视图并在那里收集操作。您并不总是返回原始类型,但始终可以使用to获取正确的集合类型。因此,xs.view.filter(p).map(f).toVector因为视图而不会创建中间体。这也在下面进行测试。还有人建议xs.flatMap(x => if (p(x)) Some(f(x)) else None)可以高效事实并非如此。下面也进行了测试。并且可以通过显式创建构建器来避免部分功能:val vb = Vector.newBuilder[String]; xs.foreach(x => if (p(x)) vb += f(x)); vb.result,其结果也列在下面。

在下表中,测试了三个条件:过滤掉任何内容,过滤掉一半,过滤掉所有内容。时间已经标准化为过滤/映射(100%=与过滤器/地图相同的时间,越低越好)。误差范围约为+ - 3%。

不同过滤器/地图替代品的效果

====================== Vector ========================
filter/map   collect  view filt/map  flatMap   builder
   100%        44%          64%        440%      30%    filter out none
   100%        60%          76%        605%      42%    filter out half
   100%       112%         103%       1300%      74%    filter out all

因此,filter/mapcollect通常非常接近(当你保持很多时collect获胜),flatMap在所有情况下都要慢得多,并创建一个建设者总是赢。 (这是专门用于Vector 。其他集合可能有一些不同的特征,但大多数的趋势将是相似的,因为操作的差异是相似的。)中的视图测试往往是一场胜利,但它们并不总是无缝地工作(除了空案例之外,它们并不比collect好。)

所以,底线:首选filter然后map如果它在速度无关紧要时有助于清晰,或者当你过滤几乎所有东西但仍希望保留东西时更喜欢速度功能(所以不想使用建设者);否则使用collect

答案 1 :(得分:3)

我想这是基于意见的,但考虑到以下定义:

scala> val l = List(1,2,3,4)
l: List[Int] = List(1, 2, 3, 4)

scala> def f(x: Int) = 2*x
f: (x: Int)Int

scala> def p(x: Int) = x%2 == 0
p: (x: Int)Boolean

你觉得哪两个更好看:

l.filter(p).map(f)

l.collect{ case i if p(i) => f(i) }

(请注意,我必须修复上面的语法,因为您需要括号和case来添加if条件。

我个人认为filter + map更易于阅读和理解。这完全取决于您使用的确切语法,但考虑到pf,您在使用filtermap时不必编写匿名函数,而你在使用收集时确实需要它们。

您还可以使用flatMap

l.flatMap(i => if(p(i)) Some(f(i)) else None)

这可能是3个解决方案中最有效的解决方案,但我发现它不如mapfilter好。

总的来说,很难说哪一个会更快,因为它取决于很多优化最终由scalac然后由JVM执行。所有3都应该非常接近,绝对不是决定使用哪一个的因素。

答案 2 :(得分:0)

filter/map看起来更干净的一种情况是,您想展平filter的结果

def getList(x: Int) = {
  List.range(x, 0, -1)
}

val xs = List(1,2,3,4)

//Using filter and flatMap
xs.filter(_ % 2 == 0).flatMap(getList)

//Using collect and flatten
xs.collect{ case x if x % 2 == 0 => getList(x)}.flatten