franklin
franklin

Reputation: 1819

How to transform a list of mixed type to tuple of lists of same type

Two objects extend a base object like this:

trait Component

class ComponentA extends Component

class ComponentB extends Component

Let's say I have some objects instantiated:

val a = new ComponentA
val a2 = new ComponentA
val b = new ComponentB

I'm given a list of type Component, like this:

val components = List[Components](a, a2, b)

What I want to do is have a function that takes a list of mixed type and transforms it into an n-tuple of lists containing only one type. So for example, components would break down like this:

transform.apply(components) = (List(a, a2), List(b))

The nonfunctional, grotesque, Frankensteinian way to do this is:

def apply(c: List[Component]) = {
  var aComponents = new ListBuffer[ComponentA]()
  var bComponents = new ListBuffer[ComponentB]()

  for (c <- components) {
    c match {
      case aType: ComponentA => aComponents += aType
      case bType: ComponentB => bComponents += bType
      case _ => None
    }
  }

  aComponents -> bComponents

}

Obviously there's tons of drawbacks to this method:

  1. In my case, I have two component types. But this case does not generalize to produce an n-tuple for n-components.
  2. It's not functional. (Uses mutable ListBuffer, doesn't leverage the Collections library.)
  3. It's ugly. Like really ugly. I cannot over-emphasize how ugly it is.

Is there a way I can get this accomplished with FP in Scala? My initial thought was to use some sort of groupBy function with an internal case/match for type inspection?

Upvotes: 0

Views: 95

Answers (3)

Leo C
Leo C

Reputation: 22439

Consider using foldRight to traverse the components list to assemble the (List[ComponentA], List[ComponentB]) tuple:

components.foldRight( (List[ComponentA](), List[ComponentB]()) )(
  (x, acc) => x match {
    case x: ComponentA => (x :: acc._1, acc._2)
    case x: ComponentB => (acc._1, x :: acc._2)
    case _ => acc
  }
)
// res1: (List[ComponentA], List[ComponentB]) =
//   (List(ComponentA@24a298a6, ComponentA@74fe5966), List(ComponentB@2bfbffb2))

Upvotes: 1

Brian McCutchon
Brian McCutchon

Reputation: 8584

I would use collect:

val aList = c.collect { case a: ComponentA => a }
val bList = c.collect { case b: ComponentB => b }
val res = (aList, bList)

Upvotes: 1

Dennis Hunziker
Dennis Hunziker

Reputation: 1293

Well, why not just use the groupBy with your class like:

c.groupBy(_.getClass).values

This will already give you a List of Lists with the grouped instances in it. Getting a tuple is more complicated as you need to know the type beforehand. There is solutions using Shapeless (Convert a Scala list to a tuple?) but not sure you really have to go that far as your original solution seems to work without an actual tuple.

Upvotes: 3

Related Questions