【问题标题】:Scala - can 'for-yield' clause yields nothing for some condition?Scala - 'for-yield' 子句可以在某些条件下不产生任何结果吗?
【发布时间】:2012-09-08 23:00:40
【问题描述】:

在 Scala 语言中,我想编写一个在给定范围内产生奇数的函数。该函数在迭代偶数时打印一些日志。函数的第一个版本是:

def getOdds(N: Int): Traversable[Int] = {
  val list = new mutable.MutableList[Int]
  for (n <- 0 until N) {
    if (n % 2 == 1) {
      list += n
    } else {
      println("skip even number " + n)
    }
  }
  return list
}

如果我省略打印日志,实现变得非常简单:

def getOddsWithoutPrint(N: Int) =
  for (n <- 0 until N if (n % 2 == 1)) yield n

但是,我不想错过日志记录部分。如何更紧凑地重写第一个版本?如果能改写成这样就好了:

def IWantToDoSomethingSimilar(N: Int) =
  for (n <- 0 until N) if (n % 2 == 1) yield n else println("skip even number " + n)

【问题讨论】:

    标签: scala yield-keyword


    【解决方案1】:
    def IWantToDoSomethingSimilar(N: Int) = 
      for {
        n <- 0 until N
        if n % 2 != 0 || { println("skip even number " + n); false }
      } yield n
    

    使用filter 而不是for 表达式会稍微简单一些。

    【讨论】:

      【解决方案2】:

      如果您想保持特征的顺序(按顺序处理奇偶数,而不是单独处理),您可以使用类似的东西(已编辑):

      def IWantToDoSomethingSimilar(N: Int) =
        (for (n <- (0 until N)) yield {
          if (n % 2 == 1) {
              Option(n) 
          } else {
              println("skip even number " + n)
              None
          }
        // Flatten transforms the Seq[Option[Int]] into Seq[Int]
        }).flatten
      

      编辑,遵循相同的概念,更短的解决方案:

      def IWantToDoSomethingSimilar(N: Int) = 
          (0 until N) map {
              case n if n % 2 == 0 => println("skip even number "+ n)
              case n => n
          } collect {case i:Int => i}
      

      【讨论】:

      • 而不是 for comprehension 和 flatten,您可以“flatMap that shit”...
      • 谢谢!我没有考虑过Option和None。不幸的是,除了 cmets 之外,代码只减少了两行。
      • 为什么 List 比 ... 嗯,(0 到 N)更好?你能给我一个链接来教育这个吗?
      • @pocorall 这是个好问题。当我发布代码时,我在想 Seq[Option[_]] 不能被展平,但事实上它可以。在这里使用 toList 没有任何好处。很抱歉 FUD。
      【解决方案3】:

      如果您想深入研究一种函数式方法,以下内容是一个很好的起点。

      首先是一些常见的定义:

          // use scalaz 7
          import scalaz._, Scalaz._
      
          // transforms a function returning either E or B into a 
          // function returning an optional B and optionally writing a log of type E
          def logged[A, E, B, F[_]](f: A => E \/ B)(
            implicit FM: Monoid[F[E]], FP: Pointed[F]): (A => Writer[F[E], Option[B]]) = 
            (a: A) => f(a).fold(
              e => Writer(FP.point(e), None), 
              b => Writer(FM.zero, Some(b)))
      
          // helper for fixing the log storage format to List
          def listLogged[A, E, B](f: A => E \/ B) = logged[A, E, B, List](f)
      
          // shorthand for a String logger with List storage
          type W[+A] = Writer[List[String], A]
      

      现在你所要做的就是编写你的过滤函数:

          def keepOdd(n: Int): String \/ Int = 
            if (n % 2 == 1) \/.right(n) else \/.left(n + " was even")
      

      您可以立即尝试:

          scala> List(5, 6) map(keepOdd)
          res0: List[scalaz.\/[String,Int]] = List(\/-(5), -\/(6 was even))
      

      然后您可以使用traverse 函数将您的函数应用于输入列表,并收集写入的日志和结果:

          scala> val x = List(5, 6).traverse[W, Option[Int]](listLogged(keepOdd))
          x: W[List[Option[Int]]] = scalaz.WriterTFunctions$$anon$26@503d0400
      
          // unwrap the results
          scala> x.run
          res11: (List[String], List[Option[Int]]) = (List(6 was even),List(Some(5), None))
      
          // we may even drop the None-s from the output
          scala> val (logs, results) = x.map(_.flatten).run
          logs: List[String] = List(6 was even)
          results: List[Int] = List(5)
      

      【讨论】:

      【解决方案4】:

      我不认为这可以通过理解来轻松完成。但是你可以使用分区。

      def getOffs(N:Int) = {
        val (evens, odds) =  0 until N partition { x => x % 2 == 0 } 
        evens foreach { x => println("skipping " + x) }
        odds
      }
      

      编辑:为避免在分区完成后打印日志消息,您可以像这样更改方法的第一行:

      val (evens, odds) = (0 until N).view.partition { x => x % 2 == 0 }
      

      【讨论】:

      • 太棒了!正如@jwinandy 提到的,日志在迭代后打印。如果在循环中运行的执行时间很长,则可能会出现问题。但是,我认为对于一般情况,这将是最简洁的代码。
      • 实际上,我的问题是实现网络爬虫并返回符合某些标准的网址。因此,替换示例 (x % 2) 的实际代码需要更多时间来执行(因为它访问网页)。当我使用 SeqView 时,标准部分会重复运行(在我的情况下,它会为每次访问 'odds' 对象访问一个网页)。
      猜你喜欢
      • 1970-01-01
      • 2012-04-27
      • 1970-01-01
      • 2023-01-24
      • 2019-11-15
      • 1970-01-01
      • 2017-10-11
      • 1970-01-01
      • 2010-11-11
      相关资源
      最近更新 更多