【问题标题】:scala return on first Some in listscala 返回列表中的第一个 Some
【发布时间】:2011-04-08 07:59:53
【问题描述】:

我有一个列表l:List[T1],目前我正在做以下事情:

myfun : T1 -> Option[T2]
val x: Option[T2] = l.map{ myfun(l) }.flatten.find(_=>true)

myfun 函数返回 None 或 Some,flatten 丢弃所有 None 并 find 返回列表的第一个元素(如果有)。

这对我来说似乎有点 hacky。我认为可能存在一些便于理解或类似的东西,这会减少浪费或更聪明。 例如:如果myfun 在列表lmap 期间返回any Some,我不需要任何后续答案。

【问题讨论】:

    标签: list scala functional-programming scala-option


    【解决方案1】:

    怎么样:

    l.toStream flatMap (myfun andThen (_.toList)) headOption
    

    Stream 是惰性的,因此它不会提前映射所有内容,但也不会重新映射所有内容。将 Option 转换为 List 以便使用 flatMap,而不是扁平化。

    【讨论】:

    • 如果我不被自己弄糊涂的话,“flatMap”也可以用在Option上,所以我认为“andThen(_.toList)”是多余的
    • val l = List(1, 2, 3, 4, 5, 6) def fun(i : Int) = { if (i == 3) Some(3) else None } println( l.flatMap(fun()).head) println(l.flatMap(fun()).headOption)
    • 导致 3 和 Some(3) 所以它确实有效,还是我搞砸了?
    • @Jens fundef,而不是函数 T => Option[T]
    • 这里开始感觉有点傻了.. 你说的 def 是什么意思?它是一个从 Int 到 Option[Int] 的函数。我可以把它写成 val fun : (Int => Option[Int]) = (i : Int) => { if (i == 3) Some(3) else None }
    【解决方案2】:

    除了使用toStream使搜索变得懒惰,我们还可以使用Stream::collectFirst

    List(1, 2, 3, 4, 5, 6, 7, 8).toStream.map(myfun).collectFirst { case Some(d) => d }
    // Option[String] = Some(hello)
    // given def function(i: Int): Option[String] = if (i == 5) Some("hello") else None
    

    这个:

    • List 转换为Stream 以便提前停止搜索。

    • 使用myFun 将元素转换为Option[T]s。

    • 收集不是None的第一个映射元素并提取它。

    Scala 2.13 开始,弃用 Streams 以支持 LazyLists,这将变为:

    List(1, 2, 3, 4, 5, 6, 7, 8).to(LazyList).map(function).collectFirst { case Some(d) => d }
    

    【讨论】:

    • 恕我直言,这个解决方案是唯一让普通读者清楚我们正在搜索第一个 Some。在使用 flatMap 和 Option.toList 的其他解决方案中,Option -> List 转换和 flatMap 混淆了原始意图:“找到第一个 Some”
    • 这个issue也是这样解决的
    • 您不需要为此转换为Stream - List 也有一个collectFirst 方法,您可以使用Function.unlift 将函数本身转换为PartialFunction。跨度>
    • @RobinGreen 如果collectFirst 匹配列表中的第一个元素,那么如果没有惰性序列(流/迭代器/惰性列表),您将浪费地在所有元素上应用function 映射即使您真的只需要为第一个元素应用它,该列表也是如此。此外,通过在collectFirst 的提升部分中应用function 操作,您还必须在collectFirst 的映射部分中再次应用它,如果function 很昂贵,这可能会很浪费。
    【解决方案3】:

    嗯,这差不多,但不完全

    val x = (l flatMap myfun).headOption
    

    但是您从 myfun 返回 Option 而不是 List,所以这可能行不通。如果是这样(我手头没有 REPL),那就试试吧:

    val x = (l flatMap(myfun(_).toList)).headOption
    

    【讨论】:

      【解决方案4】:

      嗯,理解等价物很容易

      (for(x<-l, y<-myfun(x)) yield y).headOption
      

      如果您真的进行翻译,结果与 oxbow_lakes 给出的相同。假设 List.flatmap 有合理的惰性,这是一个既干净又高效的解决方案。

      【讨论】:

      • 不幸的是,List(即collection.immutable.List)没有惰性操作。由于我不明白的原因,将 l 替换为 l.view 会导致 myfun 使用相同的参数被多次评估。
      • 视图是按名称调用的,而不是懒惰的。如果您想要最多一次评估,请使用 toStream: (l.toStream flatMap myfun).headOption
      【解决方案5】:

      截至 2017 年,之前的答案似乎已经过时。我运行了一些基准测试(1000 万个 Ints 列表,第一个匹配大致在中间,Scala 2.12.3、Java 1.8.0、1.8 GHz Intel Core i5)。除非另有说明,listmap 具有以下类型:

      list: scala.collection.immutable.List
      map: A => Option[B]
      

      只需调用列表中的map:~1000 毫秒

      list.map(map).find(_.isDefined).flatten
      

      第一次调用列表中的toStream:~1200 ms

      list.toStream.map(map).find(_.isDefined).flatten
      

      在列表中调用toStream.flatMap:~450 ms

      list.toStream.flatMap(map(_).toList).headOption
      

      在列表中调用flatMap:~100 毫秒

      list.flatMap(map(_).toList).headOption
      

      第一次调用列表中的iterator:~35 ms

      list.iterator.map(map).find(_.isDefined).flatten
      

      递归函数find():~25 ms

      def find[A,B](list: scala.collection.immutable.List[A], map: A => Option[B]) : Option[B] = {
        list match {
          case Nil => None
          case head::tail => map(head) match {
            case None => find(tail, map)
            case result @ Some(_) => result
          }
        }
      }
      

      迭代函数find():~25 ms

      def find[A,B](list: scala.collection.immutable.List[A], map: A => Option[B]) : Option[B] = {
        for (elem <- list) {
          val result = map(elem)
          if (result.isDefined) return result
        }
        return None
      }
      

      您可以通过使用 Java 而不是 Scala 集合和功能较少的样式来进一步加快速度。

      java.util.ArrayList 中循环索引:~15 ms

      def find[A,B](list: java.util.ArrayList[A], map: A => Option[B]) : Option[B] = {
        var i = 0
        while (i < list.size()) {
          val result = map(list.get(i))
          if (result.isDefined) return result
          i += 1
        }
        return None
      }
      

      循环java.util.ArrayList 中的索引,函数返回null 而不是None:~10 毫秒

      def find[A,B](list: java.util.ArrayList[A], map: A => B) : Option[B] = {
        var i = 0
        while (i < list.size()) {
          val result = map(list.get(i))
          if (result != null) return Some(result)
          i += 1
        }
        return None
      }
      

      (当然,通常将参数类型声明为java.util.List,而不是java.util.ArrayList。我在这里选择后者是因为它是我用于基准测试的类。java.util.List 的其他实现将显示不同的性能 -大多数情况会更糟。)

      【讨论】:

      • 分析 JVM 是 notoriously problematic。您使用了哪些基准测试工具? Thyme? JMH?其他?
      • 我知道 Java 基准测试存在的问题。我没有使用任何工具,只是 System.nanoTime()。这些数字中的每一个都是 100 次运行的平均值:启动 JVM,用 1000 万个随机整数填充列表,启动时钟,运行 find() 100 次,停止时钟。不是很精确,但由于存在几个数量级的差异,我想说这个简单的基准测试至少对这些方法的相对性能提供了有用的粗略概述。
      • 我现在正在旅行,但我希望我有时间上传我在接下来的几天里使用的(非常简单的)代码。我希望看到由适当工具测量的相同基准!
      • 顺便说一下,有些由于 GC(例如 toStream)而有很大的速度变化,有些几乎没有变化(例如 iterator.map)。我敢打赌,一个更细粒度的基准测试会将这些数字重现到 ±20%(对于低变化的那些)到 ±50%(对于那些具有高 GC 开销的那些)。
      • P.S.:我对所有基准测试都使用了相同的随机种子(因此也使用了相同的 1000 万个随机数)。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-05-12
      • 1970-01-01
      • 1970-01-01
      • 2014-04-03
      • 1970-01-01
      相关资源
      最近更新 更多