12

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 function is:

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
}

If I omit printing logs, the implementation become very simple:

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

However, I don't want to miss the logging part. How do I rewrite the first version more compactly? It would be great if it can be rewritten similar to this:

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

4 Answers4

9
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.

Luigi Plinge
  • 50,650
  • 20
  • 113
  • 180
6

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}
jwinandy
  • 1,739
  • 12
  • 22
  • and instead of the for comprehension and flatten, you could "flatMap that shit"... – Kim Stebel Sep 16 '12 at 14:18
  • Thanks! I didn't thought about Option and None. Unfortunately, the code only reduces two lines except comments. – pocorall Sep 16 '12 at 14:27
  • Why List is better than... hmm, (0 until N)? Could you give me a link educating about this? – pocorall Sep 16 '12 at 14:57
  • @pocorall that's a good question. When I posted the code, I was thinking Seq[Option[_]] cannot be flatten, but in facts it can. There is no avantage to use a toList here. Sorry for the FUD. – jwinandy Sep 16 '12 at 15:29
3

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)
ron
  • 9,262
  • 4
  • 40
  • 73
  • Gee, great-looking answer! I am new to scalaz. To understand pros of this approach, I have to learn scalaz first. – pocorall Sep 16 '12 at 15:20
  • pocorall: Have fun! These may help to get started. http://stackoverflow.com/questions/4863671/good-scalaz-introduction – ron Sep 16 '12 at 19:51
1

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 }
Kim Stebel
  • 41,826
  • 12
  • 125
  • 142
  • Great! As @jwinandy mentioned, the log is printed after the iteration. It may be a problem if the execution time running in the loop is long. However, I think that it would be a cleanest code for the general case. – pocorall Sep 16 '12 at 14:23
  • Actually, my problem is implementing web crawler and returns the urls meets some criterion. So, the actual code replacing that of the example (x % 2) takes more time to execute (because it accesses webpage). When I use SeqView, the criterion part runs repeatedly (in my case, it accesses a webpage for every access of 'odds' object). – pocorall Sep 16 '12 at 14:51