8 minutes read

You are already familiar with the main collection types in Scala: List, Set and Map. These collections of elements can be defined as sequences of elements or pairs. In this topic, we will look deeper into defining collections: specifically, we will discuss how to use parts of previously defined collections to construct a new one.

Empty collections

Let's start by considering those collections that have no elements.

There is a special symbol for defining an empty list in Scala: Nil, which accomplishes the same as List() or List.empty. For Set and Map we don't really have anything similar to Nil, but .empty works just fine.

Let's look at a few examples:

scala> val list: List[Int] = Nil
list: List[Int] = List()
scala> val list2 = List.empty[Int]
list2: List[Int] = List()
scala> val set = Set.empty[Char]
set: scala.collection.immutable.Set[Char] = Set()
scala> val map = Map.empty[Char, Int]
map: scala.collection.immutable.Map[Char,Int] = Map()

A big advantage of using .empty[] is that you can specify the type of elements when you're constructing a collection.

Copying

In Scala, we typically treat an existing collection as immutable: that is, we can't change any elements in place. What you can do instead is generate a new collection with copies of elements that we need, just like this:

scala> val list = List(1, 2, 3)
list: List[Int] = List(1, 2, 3)
scala> val list2 = list.tail
list2: List[Int] = List(2, 3)

Here, we generated list2 from the original one by omitting the first element and keeping the rest. We can add new elements to collections the same way:

scala> 0 :: list
res1: List[Int] = List(0, 1, 2, 3)
scala> list :+ 4
res2: List[Int] = List(1, 2, 3, 4)

A list can be copied to create a new list with various types of modifications. Let's consider some of them:

scala> list.drop(1)
res1: List[Int] = List(2, 3)
scala> list.take(1)
res2: List[Int] = List(1)
scala> list.reverse
res3: List[Int] = List(3, 2, 1)

Above, we used drop to get rid of the first element, take to get only the specified elements, and reverse to get the elements in the opposite order.

We can add a new element to a Set as well; note that adding an existing element doesn't change anything in the new collection generated:

scala> val set = Set('a', 'b')
set: scala.collection.immutable.Set[Char] = Set(a, b)
scala> val set2 = set + 'c'
set2: scala.collection.immutable.Set[Char] = Set(a, b, c)
scala> set - 'a'
res1: scala.collection.immutable.Set[Char] = Set(b)
scala> set + 'a'
res2: scala.collection.immutable.Set[Char] = Set(a, b)

It is also possible to use copying for a Map:

scala> val map = Map(1 -> 'a', 2 -> 'b', 5 -> 'a')
map: scala.collection.immutable.Map[Int,Char] = Map(1 -> a, 2 -> b, 5 -> a)
scala> val map2 = map.drop(1)
map2: scala.collection.immutable.Map[Int,Char] = Map(2 -> b, 5 -> a)
scala> val map3 = map + (6 -> 'c')
map3: scala.collection.immutable.Map[Int,Char] = Map(1 -> a, 2 -> b, 5 -> a, 6 -> c)

Note that map2 contains a subset of elements from map but they are the same old elements, not new. For map3, we added a new element but others are from map. As our collections are immutable we can't change elements themselves but we can easily reuse them.

Concatenation

You can create new collections not only by adding or removing elements but also by joining two collections of the same type. This is called concatenation.

For List, Set, and Map, use the ++ operation:

scala> List(1, 2) ++ List(2, 3)
res1: List[Int] = List(1, 2, 2, 3)
scala> List(1, 2) ++ List.empty[Int]
res2: List[Int] = List(1, 2)
scala> List(1, 2) ++ Set(1)
res3: List[Int] = List(1, 2, 1)

If we combine a List with a Set, the result will have whatever type we used first. Remember that in Set, we cannot have duplicate elements:

scala> Set(1, 2) ++ Set(2, 3)
res1: scala.collection.immutable.Set[Int] = Set(1, 2, 3)
scala> Set.empty[Int] ++ Set(2, 3)
res2: scala.collection.immutable.Set[Int] = Set(2, 3)
scala> Set(2, 3) ++ List(3)
res3: scala.collection.immutable.Set[Int] = Set(2, 3)

For Map, we can use concatenation, as well:

scala> Map('a' -> 1) ++ Map('b' -> 2)
res1: scala.collection.immutable.Map[Char,Int] = Map(a -> 1, b -> 2)
scala> Map('a' -> 1, 'b' -> 2) ++ Map('a' -> 0, 'c' -> 3)
res2: scala.collection.immutable.Map[Char,Int] = Map(a -> 0, b -> 2, c -> 3)

Notice that in the case of key duplicates in Map, we substitute the values with the last variant we have.

Conclusion

Defining an empty collection gives us the ability to construct a List, a Set, or a Map without any elements. You can't change the immutable types, but what you can do is copy an existing collection and add, remove, or update individual elements. With concatenation, you can combine two or more collections to produce new one.

14 learners liked this piece of theory. 0 didn't like it. What about you?
Report a typo