有没有理由更喜欢filter+map
:
list.filter (i => aCondition(i)).map(i => fun(i))
超过collect
? :
list.collect(case i if aCondition(i) => fun(i))
collect
(单一外观)的那个看起来更快更干净。所以我总是去collect
。
答案 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/map
和collect
通常非常接近(当你保持很多时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
更易于阅读和理解。这完全取决于您使用的确切语法,但考虑到p
和f
,您在使用filter
或map
时不必编写匿名函数,而你在使用收集时确实需要它们。
您还可以使用flatMap
:
l.flatMap(i => if(p(i)) Some(f(i)) else None)
这可能是3个解决方案中最有效的解决方案,但我发现它不如map
和filter
好。
总的来说,很难说哪一个会更快,因为它取决于很多优化最终由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