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

Dan*_*iel 28 scala

有什么理由可以选择filter+map:

list.filter (i => aCondition(i)).map(i => fun(i))
Run Code Online (Sandbox Code Playgroud)

结束collect?:

list.collect(case i if aCondition(i) => fun(i))
Run Code Online (Sandbox Code Playgroud)

与一个collect(单看)看上去更快和更清洁的我.所以我会一直这样做collect.

Rex*_*err 43

Scala的大多数集合急切地应用操作,并且(除非您使用的宏程序库为您执行此操作)不会融合操作.因此,filter随后map通常会创建两个集合(即使你使用Iterator或诸如此类的中间形式将瞬时产生,虽然只是在时间的元素),而collect不会.

另一方面,collect使用部分函数来实现联合测试,并且部分函数A => Boolean在测试集合中是否有东西时比谓词()慢.

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

xs.filter(p).map(f)
xs.collect{ case x if p(x) => f(x) }
Run Code Online (Sandbox Code Playgroud)

但如果您在线提供封闭,collect通常看起来更干净

xs.filter(x < foo(x, x)).map(x => bar(x, x))
xs.collect{ case x if foo(x, x) => bar(x, x) }
Run Code Online (Sandbox Code Playgroud)

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

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

val v = Vector.tabulate(10000)(i => ((i%100).toString, (i%7).toString))
Run Code Online (Sandbox Code Playgroud)

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

注意:可以在集合中获取惰性视图并在那里收集操作.您并不总是返回原始类型,但您始终可以使用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
Run Code Online (Sandbox Code Playgroud)

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

因此,底线:喜欢filter那么map它是否有助于画面的清晰度速度并不重要,或者更喜欢它的速度,当你过滤掉几乎所有的东西,但仍希望保持功能(所以不想使用生成器); 否则使用collect.


Reg*_*anc 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
Run Code Online (Sandbox Code Playgroud)

您觉得这两本中哪一本更好读:

l.filter(p).map(f)
Run Code Online (Sandbox Code Playgroud)

或者

l.collect{ case i if p(i) => f(i) }
Run Code Online (Sandbox Code Playgroud)

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

我个人觉得filter+map更容易阅读和理解。这完全取决于您使用的确切语法,但是考虑到pand ,您在使用orf时不必编写匿名函数,而在使用 Collect 时确实需要它们。filtermap

您还可以使用flatMap

l.flatMap(i => if(p(i)) Some(f(i)) else None)
Run Code Online (Sandbox Code Playgroud)

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

总的来说,很难说哪一个更快,因为这取决于很多优化最终由scalacJVM 执行。所有 3 个应该非常接近,并且绝对不是决定使用哪一个的因素。