Scala - 'for-yield'条款对某些条件没有任何影响吗?

时间:2012-09-16 13:25:21

标签: scala yield-keyword

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

4 个答案:

答案 0 :(得分:9)

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

使用filter代替for表达式会稍微简单一些。

答案 1 :(得分:6)

我想保持你的训练顺序(按顺序处理赔率和平均值,而不是单独处理),你可以使用类似的东西(编辑):

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

EDIT遵循相同的概念,是一个更短的解决方案:

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}

答案 2 :(得分: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)

答案 3 :(得分:1)

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

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 }