如何在列表中查找匹配元素并将其映射为Scala API方法?

22

是否有一种方法可以不使用findmap两种方法来完成以下操作?

val l = 0 to 3
l.find(_ * 33 % 2 == 0).map(_ * 33) // returns Some(66)

你为什么想要一个单一的方法来做这件事?是出于好奇还是有更深层次的原因? - Channing Walton
主要是出于好奇,但我已经做了几次这个操作,所以我打算写一个函数来完成它。我以为它是库的一部分,但找不到在哪里。 - agilefall
4个回答

32

使用collect如何?

// Returns List(66)
List(1, 2, 3) collect { case i if (i * 33 % 2 == 0) => i * 33 }

不过那会返回所有匹配项而不仅仅是第一个。

更好的答案应该基于Scala 2.9:

// Returns Some(66)
List(1, 2, 3) collectFirst { case i if (i * 33 % 2 == 0) => i * 33 }

评论中提出的解决方案是追加head以获取Scala 2.8版本,但恐怕效率不高。也许在这种情况下,您应该坚持使用自己的代码。无论如何,为了确保它返回一个选项,您不应该调用head,而应该调用headOption

// Returns Some(66)
List(1, 2, 3) collect { case i if (i * 33 % 2 == 0) => i * 33 } headOption

collect 会返回多个匹配项,而 find 只返回一个。 - Daniel C. Sobral
3
Scala 2.9增加了collectFirst方法。如果不使用collectFirst,就需要同时使用headOptioncollect来获得与原始示例相同的类型和行为,这也是达尼尔评论所暗示的。 - Kristian Domagala
1
collect将遍历整个列表。如果您使用的是2.8版本并且无法使用collectFirst,则可以执行myList.view.collect(...).headOption。通过使用视图,您可以避免遍历整个列表。 - Seth Tisue
问题在于必须执行两次操作,因为没有办法重用guard的中间结果。 - lisak
在每种情况下,您都计算了 i*33 两次。有没有可能只计算一次? - vvg

22
如果您不想多次执行map()操作(例如如果它是一个昂贵的数据库查找),您可以这样做: l.view.map(_ * 33).find(_ % 2 == 0) view使集合变成懒加载,因此最小化了map()操作的数量。

2
我认为这是这个问题最正确的答案。其他答案在大多数情况下都是无用的,因为必须执行两次操作。 - lisak
谢谢!这正是我想要的,以前从未听说过这些视图(但过去在Java/Guava中使用过类似的东西)! - Sebastien Lorber

6

嘿,看这里,我的小伙伴findMap又出现了!

/**
 * Finds the first element in the list that satisfies the partial function, then 
 * maps it through the function.
 */
def findMap[A,B](in: Traversable[A])(f: PartialFunction[A,B]): Option[B] = {
  in.find(f.isDefinedAt(_)).map(f(_))
}

请注意,与被接受的答案不同,但与其评论中提到的collectFirst方法相似,这个代码会在找到匹配元素后立即停止。

Traversable 已经定义了 collectFirst 方法,该方法与您的 findMap 所做的完全相同。 - Simão Martins

-1

这个可以做到,但如果你能告诉我你真正想要实现什么,那会更容易些:

l.flatMap(n => if (n * 33 % 2 == 0) Some(n * 33) else None).headOption

1
这将遍历整个列表。 - Seth Tisue

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