Michael Allen
Michael Allen

Reputation: 5828

Returning multiple collections from flatMap

I am working on a method that has 3 possible outcomes for multiple items: Error, Invalid and Success. For each of these I need to return a json list identifying which items were in error, invalid and successful.

My current attempt follows. I have used Object to represent the class my objects are as fully explaining would take too long. The Object class has a method process which returns a boolean to indicate success or error and throws an exception when the object is invalid:

def process(list: List[Objects]) = {
    val successIds = new ListBuffer[Int]();
    val errorIds = new ListBuffer[Int]();
    val invalidIds = new ListBuffer[Int]();

    list.foreach( item => {
        try {
            if (item.process) {
                successIds ++ item.id
            } else {
                errorIds ++ item.id
            }
        } catch {
            case e: Exception => invalidIds ++ item.id
        }
    })

    JsonResult(
        Map("success" -> successIds, 
            "failed" -> errorIds, 
            "invalid" -> invalidIds)
    ) 
}

Problem is using Mutable data structures isn't very "Scala-y". I would prefer to build up these lists in some more functional way but I am quite new to scala. Any thoughts or hints as to how this might be done?

My though is using something like the flatMap method that takes a tuple of collections and collates them in the same way the flatMap method does for a single collection:

def process(list: List[Objects]) = {

    val (success, error, invalid) = list.flatMap( item => {
        try {
            if (item.process) {
                (List(item.id), List.empty, List.empty)
            } else {
                (List.empty, List(item.id), List.empty)
            }
        } catch {
            case e: Exception => 
                (List.empty, List.empty, List(item.id))
        }
    })

    JsonResult(
        Map("success" -> success, 
            "failed" -> error, 
            "invalid" -> invalid)
    ) 
}

Upvotes: 1

Views: 1982

Answers (3)

Luigi Plinge
Luigi Plinge

Reputation: 51109

flatMap isn't what you need here - you need groupBy:

def process(list: List[Objects]) = {

  def result(x: Objects) =  
    try if (x.process) "success" else "failed"
    catch {case _ => "invalid"}     

  JsonResult(list groupBy result mapValues (_ map (_.id)))
}

Upvotes: 8

user unknown
user unknown

Reputation: 36229

Adapted to make it compile without "Objects"

def procex (item: String): Boolean = ((9 / item.toInt) < 1)

def process (list: List[String]) = {
    val li: List[(Option[String], Option[String], Option[String])] = list.map (item => {
        try {
            if (procex (item)) {
                (Some (item), None, None)
            } else {
                (None, Some (item), None)
            }
        } catch {
            case e: Exception => 
                (None, None, Some (item))
        }
    })
    li
}
// below 10 => failure
val in = (5 to 15).map (""+_).toList
// 0 to throw a little exception
val ps = process ("0" :: in)

val succeeders = ps.filter (p=> p._1 != None).map (p=>p._1)
val errors     = ps.filter (p=> p._2 != None).map (p=>p._2)
val invalides  = ps.filter (p=> p._3 != None).map (p=>p._3)

What doesn't work:

(1 to 3).map (i=> ps.filter (p=> p._i != None).map (p=>p._i))

_i doesn't work.

Upvotes: 0

Rex Kerr
Rex Kerr

Reputation: 167891

There's always recursion:

class Ob(val id: Int) { def okay: Boolean = id < 5 }


@annotation.tailrec def process(
  xs: List[Ob], 
  succ: List[Int] = Nil,
  fail: List[Int] = Nil,
  invalid: List[Int] = Nil
): (List[Int], List[Int], List[Int]) = xs match {
  case Nil => (succ.reverse, fail.reverse, invalid.reverse)
  case x :: more =>
    val maybeOkay = try { Some(x.okay) } catch { case e: Exception => None }
    if (!maybeOkay.isDefined) process(more, succ, fail, x.id :: invalid)
    else if (maybeOkay.get)   process(more, x.id :: succ, fail, invalid)
    else                      process(more, succ, x.id :: fail, invalid)
}

Which works as one would hope (skip the reverses if you don't care about order):

scala> process(List(new Ob(1), new Ob(7), new Ob(2), 
   new Ob(4) { override def okay = throw new Exception("Broken") }))

res2: (List[Int], List[Int], List[Int]) = (List(1,2),List(7),List(4))

Upvotes: 1

Related Questions