Scala:有什么理由更喜欢'filter+;在“收集”上方映射?
是否有任何理由更喜欢Scala:有什么理由更喜欢'filter+;在“收集”上方映射?,scala,Scala,是否有任何理由更喜欢过滤器+映射: list.filter (i => aCondition(i)).map(i => fun(i)) 超过收集?: list.collect(case i if aCondition(i) => fun(i)) 在我看来,带有collect(单一外观)的那款看起来更快更干净。所以我总是选择收集 我想这是基于观点的,但给出了以下定义: scala> val l = List(1,2,3,4) l: List[Int] = List(1,
过滤器+映射
:
list.filter (i => aCondition(i)).map(i => fun(i))
超过收集?:
list.collect(case i if aCondition(i) => fun(i))
在我看来,带有collect
(单一外观)的那款看起来更快更干净。所以我总是选择收集 我想这是基于观点的,但给出了以下定义:
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)
或
(注意,我必须修正上面的语法,因为您需要括号和case
来添加if
条件)
我个人觉得过滤器
+地图
更易于阅读和理解。这完全是您使用的确切语法的问题,但是给定p
和f
,您不必在使用filter
或map
时编写匿名函数,而在使用collect时确实需要它们
您还可以使用flatMap
:
l.flatMap(i => if(p(i)) Some(f(i)) else None)
这可能是三种解决方案中最有效的,但我发现它不如map
和filter
好
总的来说,很难说哪一个更快,因为这取决于scalac
和JVM最终执行了哪些优化。所有3个都应该非常接近,并且绝对不是决定使用哪一个的因素。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) }
您想在过滤第一个条目的基础上挑选第二个条目(因此过滤和映射操作都非常简单),然后我们得到下表
注意:可以将惰性视图放入集合中并在那里收集操作。您并不总是可以恢复原始类型,但您可以始终使用来获得正确的集合类型。因此xs.view.filter(p).map(f).toVector将不会因为视图而创建中间层。这一点在下文中也得到了验证。也有人建议人们可以xs.flatMap(x=>if(p(x))一些(f(x))其他没有)
,这是有效的事实并非如此。下面也对其进行了测试。可以通过显式创建一个生成器来避免分部函数:val vb=Vector.newBuilder[String];foreach(x=>if(p(x))vb+=f(x));vb.result
,其结果也列在下面
在下表中,测试了三个条件:不过滤、过滤一半、过滤所有。时间已标准化为filter/map(100%=与filter/map相同的时间,越低越好)。误差范围约为+-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
如果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
我不确定性能会有多大的不同。它们都遍历完整序列一次,并且只对满足条件的项应用谓词函数,因此它们都在做相同的基本工作量。这实际上取决于编译器有多聪明,序列有多大,条件和函数有多昂贵虽然我认为第二个可能会少分配一个序列,因为它不必为过滤器调用的返回而烦恼。@IanMcLaird我认为这是主要的争论。第二个是否真的少分配了一个序列(从而更有效)?原因与喜欢map+flattle
而不是flatMap
是一样的。因此,为什么这个参数不正确:“收集”就像一个单循环,“过滤+映射”就像两个结果循环。因此(从效率上看)人们应该更喜欢“收集”。我遗漏了什么吗?(幸好你提到了第三个备选方案。)与Rex“Mr Collections”Kerr相比是不公平的,但是没有人会期望flatMap更高效。我个人的不满是使用ell作为变量名称,因为它看起来像一个。现在,这是基于意见的!所谓“filter none”是指“filter out none”,而不是“filter Catch/retains none”。第一个代码samp
====================== 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
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