Thursday, January 26, 2012

Yet another small Scala collection trick

Once again, my colleague Danny popped up a Java collection question. Given a data map with small lists as values, what is the shortest or best way to create a new, big list containing all individual elements from the small lists?

Best thing that we found in a few minutes, using car manufacturers and models as example data:

// setup
Map < String, List > carModels =
        new HashMap < String, List < String > > ();
carModels.put("Mazda",  Arrays.asList("MX-5", "RX-8"));
carModels.put("Audi",  Arrays.asList("A1", "A4", "A8"));

// actual work in 4 lines
List < String > allModels = new LinkedList < String > ();
for (List < String > brandModels: carModels.values()) {
        allModels.addAll(brandModels);
}

Now in Scala, back home ..

I took me a little research online, because I never used Scala on a daily basis, and miss a lot of knowledge on the collections API. Luckily it's not too hard to find good pointers online, and the Scala interpreter offers code completion help.

// setup
val carModels = Map("Mazda" -> List("MX-5", "RX-8"),
        "Audi" -> List("A1", "A4", "A8"))

// actual work, one liner
val allModels = carModels.values.reduce(_ ++ _)

The end result is actually not terribly hard to understand, but let me break it into pieces before I forget it myself:

carModels.values  // easy, similar as in Java

Now we have a collection of lists, and need to put all individual model names into one big list. Explicit looping is one way, but not very elegant. Another way of operating on a list, is using the "reduce" function. In general, you simply need to supply it a function which acts on two arguments, producing a single element.

For example, sum all numbers:

def add(x: Int, y: Int): x + y  // define simple 'add' function
List(1, 3, 5).reduce(add)   // evaluates to 9

So, this could lead to the following construction (with ++ for joining):

carModels.values.reduce( (models1, models2) => models1 ++ models2)

And thanks to implicit naming using underscores, we can end with:

carModels.values.reduce( _ ++ _ ) 

I still have to figure out what the difference is between the "++" and ":::" operators to join lists, and have many (!) other things to learn on Scala.

Feedback or corrections are very welcome!

2 comments:

Ronald Steinhau said...

I would use ...values.flatten, its even shorter snd probably faster.
Regards Ronald

Tung said...

Thanks Ronald, I wasn't aware of such a method!

A little reminder for myself:

http://www.scala-lang.org/api/current/index.html#scala.collection.generic.GenericTraversableTemplate