Scala snippets 3: Lists together with Map, flatmap, zip and reduce

5 minute read

You can’t really talk about scala without going into the details of the Map, flatMap, zip and reduce functions. With these functions it is very easy to process the contents of lists and work with the Option object. Note that on this site you can find some more snippets:

  1. Scala snippets 1: Folding
  2. Scala snippets 2: List symbol magic
  3. Scala snippets 3: Lists together with Map, flatmap, zip and reduce
  4. Scala snippets 4: Pimp my library pattern with type classes

Lets just start with the map option. With a map option we apply a function to each element of the list and return that as a new list.

We can use this to multiply each value in the list:

scala> list1
res3: List[Int] = List(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

scala> list1.map(x=>x*x)
res4: List[Int] = List(0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100)

Some function you apply to a list might result in Option elements. Take for instance the following function:

scala> val evenify = (x:Int) => if (x % 2 == 0) Some(x) else None
evenify: Int => Option[Int] = <function1>

scala> list1.map(evenify)
res6: List[Option[Int]] = List(Some(0), None, Some(2), None, Some(4), None, Some(6), None, Some(8), None, Some(10))

The problem in this case is that we’re often not that interested in the None results in our list. But how do we easily get them out? For this we can use flatMap. With flatMap we can process lists of sequences. We apply the provided function on each element of each sequence in the list and return a list that contains the elements of each sequence of the original list. An example is much easier to understand:

scala> val list3 = 10 to 20 toList
list3: List[Int] = List(10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20)

scala> val list2 = 1 to 10 toList
list2: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

scala> val list4 = List(list2, list3)
list4: List[List[Int]] = List(List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10), List(10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20))

scala> list4.flatMap(x=>x.map(y=>y*2))
res2: List[Int] = List(2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40)

As you can see we have two lists. On this list we call the flatMap function. The flatMap processes each of the two entries individually. On each of te individual lists we call the map function to duplicate each entry. The final result is a single list that contains all entries flattened to a single list.

Now lets look back at the evenify function we saw earlier and the list of Option elements we had.

scala> val list1 = 1 to 10 toList
list1: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

scala> list1.map(evenify)
res3: List[Option[Int]] = List(None, Some(2), None, Some(4), None, Some(6), None, Some(8), None, Some(10))

scala> val list2 = list1.map(evenify)
list2: List[Option[Int]] = List(None, Some(2), None, Some(4), None, Some(6), None, Some(8), None, Some(10))

scala> list2.flatMap(x => x)
res6: List[Int] = List(2, 4, 6, 8, 10)

Easy right. And ofcourse we can also write this in one line.

scala> list1.flatMap(x=>evenify(x))
res14: List[Int] = List(2, 4, 6, 8, 10)

As you can see, not that difficult. Now lets look at the other couple of functions you can use on lists. The first one is zip. And as the name implies with this function we can combine two lists together.

scala> val list = "Hello.World".toCharArray
list: Array[Char] = Array(H, e, l, l, o, ., W, o, r, l, d)

scala> val list1 = 1 to 20 toList
list1: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20)

scala> list.zip(list1)
res30: Array[(Char, Int)] = Array((H,1), (e,2), (l,3), (l,4), (o,5), (.,6), (W,7), (o,8), (r,9), (l,10), (d,11))

scala> list1.zip(list)
res31: List[(Int, Char)] = List((1,H), (2,e), (3,l), (4,l), (5,o), (6,.), (7,W), (8,o), (9,r), (10,l), (11,d))

As soon as one list reaches the end the zip function stops. We’ve also got a zipAll function, which also processes the left over elements of the larger list:

scala> list.zipAll(list1,'a','1')
res33: Array[(Char, AnyVal)] = Array((H,1), (e,2), (l,3), (l,4), (o,5), (.,6), (W,7), (o,8), (r,9), (l,10), (d,11), (a,12), (a,13), (a,14), (a,15), (a,16), (a,17), (a,18), (a,19), (a,20))

If the list with characters is exhausted we’ll place the letter ‘a’ if the list of integers is exhausted, we’ll place a 1. We’ve got one final zip function to explore and that’s zipWithIndex. Once again, the name pretty much sums up what will happen. An index element will be added:

scala> list.zipWithIndex
res36: Array[(Char, Int)] = Array((H,0), (e,1), (l,2), (l,3), (o,4), (.,5), (W,6), (o,7), (r,8), (l,9), (d,10))

So on to the last of the functions to explore:reduce. With reduce we process all the elements in the list and return a single value. With reduceLeft and reduceRight we can force the direction in which the values are processed (with reduce this isn’t guaranteed):

scala> list1
res51: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20)

scala> val sum = (x:Int, y:Int) => {println(x,y) ; x + y}
sum: (Int, Int) => Int = <function2>

scala> list1.reduce(sum)
(1,2)
(3,3)
(6,4)
(10,5)
(15,6)
(21,7)
(28,8)
(36,9)
(45,10)
(55,11)
(66,12)
(78,13)
(91,14)
(105,15)
(120,16)
(136,17)
(153,18)
(171,19)
(190,20)
res52: Int = 210

scala> list1.reduceLeft(sum)
(1,2)
(3,3)
(6,4)
(10,5)
(15,6)
(21,7)
(28,8)
(36,9)
(45,10)
(55,11)
(66,12)
(78,13)
(91,14)
(105,15)
(120,16)
(136,17)
(153,18)
(171,19)
(190,20)
res53: Int = 210

scala> list1.reduceRight(sum)
(19,20)
(18,39)
(17,57)
(16,74)
(15,90)
(14,105)
(13,119)
(12,132)
(11,144)
(10,155)
(9,165)
(8,174)
(7,182)
(6,189)
(5,195)
(4,200)
(3,204)
(2,207)
(1,209)
res54: Int = 210

Besides these functions we also have reduceOption (and also in the reduceLeftOption and reduceRightOption variants). These functions will return an Option instead of the value. This can be used to safely process empty lists, which will result None.

scala> list1.reduceRightOption(sum)
(19,20)
(18,39)
(17,57)
(16,74)
(15,90)
(14,105)
(13,119)
(12,132)
(11,144)
(10,155)
(9,165)
(8,174)
(7,182)
(6,189)
(5,195)
(4,200)
(3,204)
(2,207)
(1,209)
res65: Option[Int] = Some(210)

scala> val list3 = List()
list3: List[Nothing] = List()

scala> list3.reduceRightOption(sum)
res67: Option[Int] = None

Enough for this snippet and for exploring the List/Collections API for now. In the next snippet we’ll look at some Scalaz stuff, since even though the library has a bit of a bad name of being complex, it provides some really nice features.

Updated: