假设我有一个由 A
类型的元素和一个 A -> Option<B>
类型的函数f
组成的序列mySeq
,并且我想要类型 Option<B>
的第一个结果,即通过将f
应用于序列的所有元素而产生的Some
,否则如果没有找到这样的结果,则None
。
在 F# 中,这由 tryPick
函数整齐地处理:
mySeq |> Seq.tryPick f
在 Scala 中,我能找到的最好的方法是:
mySeq.iterator.map(f).find(_.isDefined).flatten
有没有更好的方法可以做到这一点?
编辑:即使在找到第一个isDefined
列表后计算整个列表结果的解决方案也是不可接受的。
编辑:从Bogdan Vakulenko和Jasper-M的评论来看,一个不错的选择似乎是
mySeq.iterator.flatMap(f).nextOption()
您也可以自己实现它:
@tailrec
def tryPick[A, B](fn: A => Option[B], seq: Seq[A]): Option[B] =
seq match {
case Nil => None // Only in case an empty sequence was passed initially
case head +: Nil => fn(head)
case head +: tail => fn(head) match {
case Some(output) => Some(output)
case None => tryPick(fn, tail)
}
}
然后像这样使用它:
scala> def myFn(int: Int) = if (int > 2) Some(int) else None
myFn: (int: Int)Option[Int]
scala> tryPick(myFn, List(1, 0, -2, 3))
res0: Option[Int] = Some(3)
tryPick
几乎等同于collectFirst
。除了collectFirst
适用于PartialFunction
.因此,您最接近
seq.collectFirst((f _).unlift) // when f is a method
seq.collectFirst(f.unlift) // when f is a function
seq.collectFirst(Function.unlift(f)) // when you're on Scala 2.12 or lower, or just like this better than the previous 2...
看:
scala> def f(a: Int) = {println(s"f($a)"); Option.when(a/2*2 == a)("x" * a)}
f: (a: Int)Option[String]
scala> List(1, 2, 3, 4, 5).collectFirst((f _).unlift)
f(1)
f(2)
res1: Option[String] = Some(xx)
mySeq.iterator.flatMap(a => f(a)).toSeq.headOption
不幸的是,必须调用.toSeq
,因为Iterator
没有headOption
方法。
但是 <-- 仅在 scala 2.13 之前toSeq
返回Iterator
的Stream
,这是懒惰计算的,因此不会发生不必要的计算。
你根本不需要迭代器,你可以做
mySeq.find(f(_).isDefined)
这是一个测试,表明find
在没有迭代器的情况下立即工作(时间以微秒为单位(
val mySeq = (1 to 10000000).toSeq
def f(int: Int): Option[Int] = {
if (int < 5) Some(int * int) else None
}
val t1 = System.nanoTime
/* computes all elements */
val res1 = mySeq.filter(f(_).isDefined).head
val duration1 = (System.nanoTime - t1) / 1e6d
println(s"Computes all elements before $res1 in " + duration1)
val t2 = System.nanoTime
/* does not compute all elements */
val res2 = mySeq.find(f(_).isDefined).head
val duration2 = (System.nanoTime - t2) / 1e6d
println(s"Returns $res2 immediately after " + duration2)
结果
Computes all elements before 1 in 143.293762
Returns 1 immediately after 1.192212