Can I map over a list and expand it at the same time in Scala?

Viewed 234

I have a list of Tuple-2 like this:

val stuff = List( ("!thing","value"), ("otherthing","value") )

I want to pass over this list and create objects from the contents like this:

val processed = stuff.map{ case (label, value) => 
  if(label.startsWith("!"))
    BigThing(label.tail,value) // extends trait Thing
  else
    LittleThing(label,value) . // extends trait Thing
}

So far so good. Now the question...

While I'm mapping over these tuples, if the label starts with "!", I also want to create a Bonus(label.tail) object (also extends trait Thing) and add this object to the resulting list. This means that an input list of n items may result a list of >n items out. What's the most streamlined functional solution to this?

The final output desired is an expanded list of Thing. So the example above would theoretically produce (order unimportant):

List( BigThing("thing","value"), Bonus("thing"), LittleThing("otherthing","value") ) 
3 Answers

I would use a flatMap for this:

val processed = stuff.flatMap { case (label, value) =>
  if (label.startsWith("!"))
    List(BigThing(label.tail, value), Bonus(label.tail))
  else
    List(LittleThing(label, value))
}

Result:

List(BigThing(thing,value), Bonus(thing), LittleThing(otherthing,value))

How about foldLeft:

val processed = stuff.foldLeft(List.empty[Thing]) { case (list, (label, value)) =>
  if (label.startsWith("!")) {
    list ++ (BigThing(label.tail, value) :: Bonus(label.tail) :: Nil)
  } else list :+ LittleThing(label,value)
}

As there are already answers for the question, but if we notice the return type is List[Product with Serializable] because you try to create a list containing unrelated case classes and it is not coming into the best practice.

Ex:

scala> val processed = stuff.flatMap { case (label, value) =>
     |   if (label.startsWith("!"))
     |     List(BigThing(label.tail, value), Bonus(label.tail))
     |   else
     |     List(LittleThing(label, value))
     | }
processed: List[Product with Serializable] = List(BigThing(thing,value), Bonus(thing), LittleThing(otherthing,value))

We can remove it by providing return type explicitly. Since we are returning List of different case classes, we can use Lis[Any].

Ex:

scala> trait Thing extends Product with Serializable
defined trait Thing

scala> case class BigThing(id: String, value: String) extends Thing
defined class BigThing

scala> case class Bonus(id: String) extends Thing
defined class Bonus

scala> case class LittleThing(id: String, value: String) extends Thing
defined class LittleThing

scala> val stuff = List( ("!thing","value"), ("otherthing","value") )
stuff: List[(String, String)] = List((!thing,value), (otherthing,value))

scala>  val result = stuff.flatMap { case (label, value) =>
     |         label match {
     |           case lable if label.startsWith("!") => List(BigThing(label.tail, value), Bonus(label.tail))
     |           case _ => List(LittleThing(label, value))
     |         }
     |       }
result: List[Thing] = List(BigThing(thing,value), Bonus(thing), LittleThing(otherthing,value))
Related