【问题标题】:How to yield a single element from for loop in scala?如何从scala中的for循环中产生单个元素?
【发布时间】:2012-11-12 12:13:40
【问题描述】:

很像这个问题:

Functional code for looping with early exit

说代码是

def findFirst[T](objects: List[T]):T = {
  for (obj <- objects) {
    if (expensiveFunc(obj) != null) return /*???*/ Some(obj)
  }
  None
}

如何在 scala 中从这样的 for 循环中生成单个元素?

我不想使用 find,正如原始问题中提出的那样,我很好奇它是否以及如何使用 for 循环来实现。

* 更新 *

首先,感谢所有 cmets,但我想我的问题并不清楚。我正在拍摄这样的东西:

val seven = for {
    x <- 1 to 10
    if x == 7
} return x

这不会编译。这两个错误是: - 返回外部方法定义 - 方法 main 有返回语句;需要结果类型

我知道 find() 在这种情况下会更好,我只是在学习和探索语言。在具有多个迭代器的更复杂的情况下,我认为使用 for 查找实际上是有用的。

感谢评论者,我将开始赏金以弥补问题的不良姿势:)

【问题讨论】:

  • 代码示例精确地从for 循环返回单个元素。你想做什么不同的事情?
  • 将返回类型更改为Option[T],您编写的代码将起作用。不确定我是否理解您的问题。
  • 我建议您使用带有find 的版本,同时避免使用nullreturn 以使您的代码更符合Scala 的习惯。
  • 完全同意 Jesper 的观点。你的问题听起来像:是的,我知道如何以简洁明了的方式解决这个问题,但是谁能教我让这个单调和纠结?没有比基于 `find 的解决方案更好的解决方案了。

标签: scala


【解决方案1】:

如果您想使用for 循环,它使用比.find.filter 等的链式调用更好的语法,有一个巧妙的技巧。与其迭代像列表这样的严格集合,不如迭代像迭代器或流这样的惰性集合。如果你从一个严格的集合开始,让它变得懒惰,例如.toIterator.

让我们看一个例子。

首先让我们定义一个“嘈杂”的 int,它会在调用时显示给我们

def noisyInt(i : Int) = () => { println("Getting %d!".format(i)); i }

现在让我们用其中的一些来填写一个列表:

val l = List(1, 2, 3, 4).map(noisyInt)

我们要寻找第一个是偶数的元素。

val r1 = for(e <- l; val v = e() ; if v % 2 == 0) yield v

以上行结果:

Getting 1!
Getting 2!
Getting 3!
Getting 4!
r1: List[Int] = List(2, 4)

...表示访问了所有元素。这是有道理的,因为结果列表包含 all 个偶数。这次让我们迭代一个迭代器:

val r2 = (for(e <- l.toIterator; val v = e() ; if v % 2 == 0) yield v)

这会导致:

Getting 1!
Getting 2!
r2: Iterator[Int] = non-empty iterator

请注意,循环只执行到它可以确定结果是空迭代器还是非空迭代器。

要获得第一个结果,您现在只需调用r2.next

如果您想要 Option 类型的结果,请使用:

if(r2.hasNext) Some(r2.next) else None

编辑您在此编码中的第二个示例只是:

val seven = (for {
    x <- (1 to 10).toIterator
    if x == 7
} yield x).next

...当然,如果您要使用.next,您应该确保至少有一个解决方案。或者,使用为所有Traversables 定义的headOption 来获得Option[Int]

【讨论】:

  • 差不多了,是不是可以这样写,使得返回值的类型实际上是单个Int,而不是Vector[Int]或者List[Int]?
  • 在我的示例中,结果类型是Iterator。在其上调用.next 以获取Int(如果没有解决方案将抛出异常)。
  • 我明白了...有没有返回 None 而不是异常的方法? (如果找到,还有 Some[Int])。如果不是,它在我自己的“OptionIterator”特征和隐式转换中是可行的,对吧?
  • 曾经有一个方法'headOpt',但它在很久以前就被弃用了。您确实可以通过隐式转换自己重新定义它。
【解决方案2】:

您可以将列表转换为流,以便 for 循环包含的任何过滤器仅按需评估。但是,从流中产生将始终返回一个流,而您想要的是我想一个选项,因此,作为最后一步,您可以检查生成的流是否具有至少一个元素,并将其头部作为选项返回。 headOption 函数正是这样做的。

def findFirst[T](objects: List[T], expensiveFunc: T => Boolean): Option[T] =
    (for (obj <- objects.toStream if expensiveFunc(obj)) yield obj).headOption

【讨论】:

    【解决方案3】:

    为什么不完全按照您上面的草图进行操作,即尽早从循环中提取return?如果您对 Scala 在后台实际执行的操作感兴趣,请使用 -print 运行您的代码。 Scala 将循环脱糖成foreach,然后使用异常提前离开foreach

    【讨论】:

      【解决方案4】:

      所以你要做的是在你的条件满足后打破一个循环。这里的答案可能是您正在寻找的。 How do I break out of a loop in Scala?.

      总体而言,Scala 中的 for comprehension 被转换为 map、flatmap 和 filter 操作。所以除非你抛出异常,否则不可能突破这些函数。

      【讨论】:

        【解决方案5】:

        如果您想知道,LineerSeqOptimized.scala 中的 find 是这样实现的; List 继承了哪个

        override /*IterableLike*/
          def find(p: A => Boolean): Option[A] = {
            var these = this
            while (!these.isEmpty) {
              if (p(these.head)) return Some(these.head)
              these = these.tail
            }
            None
          }
        

        【讨论】:

          【解决方案6】:

          这是一个可怕的黑客攻击。但它会得到你想要的结果。

          通常你会使用 Stream 或 View 并且只计算你需要的部分。

          def findFirst[T](objects: List[T]): T = {
          
          def expensiveFunc(o : T)  = // unclear what should be returned here
          
          case class MissusedException(val data: T) extends Exception
          
          try {
            (for (obj <- objects) {
              if (expensiveFunc(obj) != null) throw new MissusedException(obj)
            })
            objects.head // T must be returned from loop, dummy
          } catch {
            case MissusedException(obj) => obj
          }
          

          }

          【讨论】:

          • 从闭包返回是这样实现的。
          【解决方案7】:

          为什么不喜欢

          object Main {     
            def main(args: Array[String]): Unit = {
              val seven = (for (
              x <- 1 to 10
              if x == 7
              ) yield x).headOption
            }
          }
          

          如果值满足条件,变量seven 将是一个持有Some(value) 的选项

          【讨论】:

          • 这(在推导中使用'if')实际上是一个好主意,应该是首先考虑的事情。但是,这并不总是可能的 - 在某些情况下会创建多个匹配项,而第二级技巧(在@dapek 的答案中提出)是对它们进行惰性评估。
          【解决方案8】:

          希望能帮到你。

          我认为......没有“回报”的暗示。

          object TakeWhileLoop extends App {
              println("first non-null: " + func(Seq(null, null, "x", "y", "z")))
          
              def func[T](seq: Seq[T]): T = if (seq.isEmpty) null.asInstanceOf[T] else
                  seq(seq.takeWhile(_ == null).size)
          }
          
          object OptionLoop extends App {
              println("first non-null: " + func(Seq(null, null, "x", "y", "z")))
          
              def func[T](seq: Seq[T], index: Int = 0): T = if (seq.isEmpty) null.asInstanceOf[T] else
                  Option(seq(index)) getOrElse func(seq, index + 1)
          }
          
          object WhileLoop extends App {
              println("first non-null: " + func(Seq(null, null, "x", "y", "z")))
          
              def func[T](seq: Seq[T]): T = if (seq.isEmpty) null.asInstanceOf[T] else {
                  var i = 0
                  def obj = seq(i)
                  while (obj == null)
                      i += 1
                  obj
              }
          }
          

          【讨论】:

            【解决方案9】:
            objects iterator filter { obj => (expensiveFunc(obj) != null } next
            

            诀窍是在集合上获得一些惰性求值视图,可以是迭代器或流,或 objects.view。过滤器只会在需要时执行。

            【讨论】:

              猜你喜欢
              • 2017-02-07
              • 1970-01-01
              • 2010-10-31
              • 2021-01-05
              • 2023-04-02
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2020-04-18
              相关资源
              最近更新 更多