Scala 是否有与 F# 的 Seq.tryPick 函数等效的高效函数?

问题描述 投票:0回答:4

假设我有一个由

mySeq
类型的元素和
A
类型的函数
f
组成的序列
A -> Option<B>
,并且我想要
Option<B>
类型的第一个结果,即应用
 得到的 
Some
 f
到序列的所有元素,否则
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()
scala sequence
4个回答
2
投票

您也可以自己实现:

@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)

2
投票

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)

1
投票

mySeq.iterator.flatMap(a => f(a)).toSeq.headOption

不幸的是,必须调用

.toSeq
,因为
Iterator
没有
headOption
方法。

但是

toSeq
Stream
返回
Iterator
,这是延迟评估的,因此不会发生不必要的计算。
<-- only before scala 2.13


-1
投票

你根本不需要迭代器,你可以这样做

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
© www.soinside.com 2019 - 2024. All rights reserved.