如何在Scala中结合使用filter和map?

47

我在Scala中有一个List[Int]。该列表是List(1,2,3,4,5,6,7,8,9,10)。我想要对该列表进行filter操作,使其只包含偶数。并且我想将这些数字乘以2。

这是否可能?


6
试着使用 collect,它结合了 filtermap 的特性。 - cmbaxter
1
我正在研究它。你能给个例子吗?文档中提到了“partial function”,但我不知道它是什么意思。 - odbhut.shei.chhele
部分函数指的是仅在所有可能输入的子集中定义的函数,当输入不在此子集之内时,它将不返回任何结果。请参阅此处以获取更多信息:http://alvinalexander.com/scala/how-to-define-use-partial-functions-in-scala-syntax-examples - Khoa
6个回答

83

正如我在评论中所述,collect 应该可以满足你的需求:

list.collect{
  case x if x % 2 == 0 => x*2
}

collect 方法允许您同时指定匹配元素的条件(filter)和修改符合条件的值(map)。

正如@TravisBrown建议的那样,您也可以使用 flatMap,特别是在条件更复杂且不适合作为守卫条件的情况下。 对于您的示例,可以使用以下内容:

list.flatMap{
  case x if x % 2 == 0 => Some(x*2)
  case x => None
}

7
有时候很难或不方便用某种方式表达你的条件,使它可以作为一个保护器。因此,在我看来,一个好的答案应该包括使用 flatMapOption(在 Scala 中这也是一种常见惯用语法,因此识别它非常有用)。 - Travis Brown
@TravisBrown,说得好。我在我的答案中包含了它作为一个选项。谢谢。 - cmbaxter
我的最终列表大小是多少,5还是10? - odbhut.shei.chhele
@eddard.stark,当完成时,您的最终列表将拥有5个元素。 - cmbaxter
9
还有一个函数叫做withFilter,与filter().map()相比,它避免了一个迭代过程和创建一个经过筛选的列表作为中间步骤:list.withFilter(_ % 2 == 0).map(_ * 2) - Sascha Kolberg

13

以下是一个for循环推导式(在内部展开为mapwithFilter的组合):

for (x <- xs if x % 2 == 0) yield x*2

也就是说

xs.withFilter(x => x % 2 == 0).map(x => x*2)

2
正如@cmbaxter所说,collect完全符合您的需求。 collect的另一个好处是,如果您按类过滤,它会自动计算出结果类型。
scala> trait X
// defined trait X

scala> class Foo extends X
// defined class Foo

scala> class Bar extends X
// defined class Bar

scala> val xs = List(new Foo, new Bar, new Foo, new Bar)
// xs: List[X] = List(Foo@4cfa8227, Bar@78226c36, Foo@3f685162, Bar@11f406f8)

scala> xs.collect { case x: Foo => x }
// res1: List[Foo] = List(Foo@4cfa8227, Foo@3f685162)

就技术水平而言,过滤器并不那么智能(参见 List[Foo]List[X]):

scala> xs.filter { case x: Foo => true; case _ => false }
// res3: List[X] = List(Foo@4cfa8227, Foo@3f685162)

只是一个快速的猜测,我认为这是因为 { case x: Foo => x } 被定义为 PartialFunction[X, Foo]Foo 是唯一可能的返回类型,所以 collect 已经知道它必须构建一个可以包含 Foo 的列表。另一方面,filter 没有这样的类型知识。 - Adowrath

2
这应该能为你做到:
当条件为p%2 == 0时(仅获取偶数),首先进行过滤。
然后使用map将这些偶数乘以2。
var myList = List(1,2,3,4,5,6,7,8,9,10).filter(p => p % 2 == 0).map(p => {p*2})

1
我倾向于对我看到的所有仅包含代码的答案进行投票否决,但标题中的“如何组合”部分意味着这甚至不是一个真正的答案。 - Travis Brown
我给你点了赞,但这不是我在寻找的。谢谢你。 - odbhut.shei.chhele
@travisBrown 你应该给我30秒钟来编辑我的回答 :) 我现在添加了解释。 - Cacho Santa
@cacho,这并不是个人攻击,但我也认为占位答案不是一个特别好的主意。 :) - Travis Brown
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Cacho Santa

1
怎么样,来个老式的折叠效果?
xs.foldLeft(List[Y]()) { (ys, x) => 
    val z = calculateSomethingOnX(x)
    if (someConditionOnZ(z)) 
        Y(x, z) :: ys
    else 
        ys
}

1

我倾向于喜欢过滤器方法。

val list1 = List(1,2,3,4,5,6,7,8,9,10)
list1.filter(x => x%2 == 0).map(_*2)

这与2年前elm或cacho提供的答案在实质上有何不同? - jwvh
@jwvh 谢谢您的评论。这是一种略微不同的方法。这是个人喜好问题,这也是我使用“倾向于喜欢”这个短语的原因。我现在发布了回复,因为我也有这个问题,我想其他人在未来可以从不同的选择中受益。 - Dean Sha
但这并不是另一种选择。它与cacho的建议完全相同。唯一的区别是在map lambda中使用下划线。为什么你不在filter lambda中也这样做很奇怪。 - jwvh
我认为collectwithFilterfiltermap更高效,因为filtermap会迭代两次数组。 - ruloweb

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