Scala - can 'for-yield' clause yields nothing for some condition?

后端 未结 4 1816
太阳男子
太阳男子 2021-02-19 22:48

In Scala language, I want to write a function that yields odd numbers within a given range. The function prints some log when iterating even numbers. The first version of the fu

相关标签:
4条回答
  • 2021-02-19 23:00

    I don't think this can be done easily with a for comprehension. But you could use partition.

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

    EDIT: To avoid printing the log messages after the partitioning is done, you can change the first line of the method like this:

    val (evens, odds) = (0 until N).view.partition { x => x % 2 == 0 }
    
    0 讨论(0)
  • 2021-02-19 23:01

    I you want to keep the sequentiality of your traitement (processing odds and evens in order, not separately), you can use something like that (edited) :

    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, following the same concept, a shorter solution :

    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}
    
    0 讨论(0)
  • 2021-02-19 23:15

    If you will to dig into a functional approach, something like the following is a good point to start.

    First some common definitions:

        // 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]
    

    Now all you have to do is write your filtering function:

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

    You can try it instantly:

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

    Then you can use the traverse function to apply your function to a list of inputs, and collect both the logs written and the results:

        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)
    
    0 讨论(0)
  • 2021-02-19 23:19
    def IWantToDoSomethingSimilar(N: Int) = 
      for {
        n <- 0 until N
        if n % 2 != 0 || { println("skip even number " + n); false }
      } yield n
    

    Using filter instead of a for expression would be slightly simpler though.

    0 讨论(0)
提交回复
热议问题