1. Introduction

Scala has a very rich collections library, located under the scala.collection package.

In this tutorial, we’ll do a quick overview of the common Scala collections and how to use them.

2. Types of Scala Collections

There are two types of collections in Scala – mutable and immutable. Let’s point out some differences.

2.1. Mutable Collections

A mutable collection updates or extends in place. This means we can add, change or remove elements of a collection as a side effect. These collections have operations that change the collection in place. All mutable collection classes are present in the scala.collection.mutable package.

2.2. Immutable Collections

All immutable collections are present under scala.collection.immutable. We can perform operations such as additions, removals, or updates, but these operations always return a new collection and leave the old collection unchanged.

3. Scala Collection Hierarchy Overview

Scala’s collection classes begin with the Traversable and Iterable traits. These traits branch into three main categories: List, Set, and Map.

The Traversable trait allows us to traverse an entire collection. It’s a base trait for all other collections. It implements the common behavior in terms of a foreach method.

The Iterable trait is the next trait from the top of the hierarchy and a base trait for iterable collections. It defines an iterator which allows us to loop through a collection’s elements one at a time. When we use an iterator, we can traverse the collection only once. This is mainly because each element gets processed during the iteration process.

Now, let’s explore some of the most common immutable collections of the Scala library.

4. Most Commonly Used Scala Collections

4.1. List

Scala lists internally represent an immutable linked list. It maintains the order of elements and can contain duplicates as well. This class is optimal for last-in-first-out (LIFO), stack-like access patterns.

It also implements structural sharing of the tail list. This means that many operations have either a constant memory footprint or no memory footprint at all.

A list has O(1) prepend and head/tail access. Most other operations are O(n) though; this includes length, append, reverse, and also the index-based lookup of elements.

We can, for example, declare a list of integers:

val numbersList: List[Int] = List(1, 2, 3 ,4)
val emptyList: List[Int] = List() // Empty List

This List class comes with two implementing case classes, scala.Nil and scala.::, that implement the abstract members isEmpty, head, and tail. A Scala list containing elements can be represented using x :: xs, where x is the head and the xs is the remaining list. Nil represents an empty list:

val numbersList: List[Int] = 1 :: 2 :: 3 :: 4 :: Nil  // List of Integers
val emptyList: List[Int] = Nil  // Empty List
val x :: xs = numbersList
assert(x == 1) // true
assert(xs == List(2, 3, 4)) // true

In the above example, we can represent the numbersList using the x :: xs notation. While printing the value of x and xs, we find that x is the head of the list, and xs is the remaining list.

There are three basic operations on lists:

No. Method Description
1 head Returns the first element of the List
2 tail Returns a List consisting of all elements except head (the first element)
3 isEmpty Returns true if the List is empty

Let’s try using each one in turn:

val numbersList: List[Int] = 1 :: 2 :: 3 :: 4 :: Nil
assert(numbersList.head == 1) // true
assert(numbersList.tail == List(2, 3, 4)) // true
assert(numbersList.isEmpty) // false

Other common operations include concatenating two lists, creating uniform lists, and reversing a list:

List(1,2) ::: List(3,4) // List(1, 2, 3, 4)

List.fill(3)(100) // List(100, 100, 100)

List(1,2,3,4).reverse // List(4, 3, 2, 1)

We can find the complete list of Scala List methods in the ScalaDoc.

4.2. Set

Scala Set is a collection of unique elements. By default, Scala uses an immutable set. It doesn’t maintain any order for storing elements.

We can declare an immutable set as:

val emptySet: Set[Int] = Set() // Empty set
val numbersSet: Set[Int] = Set(1, 2, 3, 4) // Set of integers

If we want to use a mutable Set, we need to import it from the collection.mutable explicitly:

val mutableSet = collection.mutable.Set(1, 2, 3)

The operations on a Set are similar to the ones on the List:

No. Method Description
1 head Returns the first element of a Set
2 tail Returns a Set consisting of all elements except head (the first element)
3 isEmpty Returns true if the Set is empty

So, let’s try them out:

Set(1, 2, 3, 4).head // 1
Set(1, 2, 3, 4).tail // Set(2, 3, 4)
Set(1, 2, 3, 4).isEmpty // false

The complete list of methods of Scala Set is in the ScalaDoc.

4.3. Map

A Map is a collection of key/value pairs where keys are always unique. Scala provides mutable and immutable versions of it. By default, an immutable version of the map is imported:

val immutableMap = Map(1 -> "a", 2 -> "b")
val mutableMap = collection.mutable.Map(1 -> "a", 2 -> "b")

The methods for working with maps are bit different:

No. Method Description
1 keys Returns an iterable containing all keys of the Map
2 values Returns an iterable containing all values of the Map
3 isEmpty Returns true if the Map is empty

Let’s see how these methods work:

Map(1 -> "a", 2 -> "b").keys // res0: Iterable[Int] = Set(1, 2)

Map(1 -> "a", 2 -> "b").values // res1: Iterable[String] = Iterable(a, b)

Map(1 -> "a", 2 -> "b").isEmpty // false

The get method returns an optional value. Its signature in the Map trait is as follows:

def get(key: K): Option[V]

When the key exists, it returns the value in Some context, whereas if the key does not exist, it returns None:

Map(1 -> "a", 2 -> "b").get(1) // Some(a)

Map(1 -> "a", 2 -> "b").get(3) // None

We can find the complete list of methods of Scala Map in the ScalaDoc.

4.4. Tuple

A Tuple is a collection that gives us a way to store different items in the same container. It combines a fixed number of items. We can pass this as a whole, and there’s no need to declare a class separately.

Scala 2.x has classes named Tuple2, Tuple3 … up to Tuple22.

If we just place some elements inside parentheses, we get a Tuple. A tuple of int and String would look like:

val t1 = (1, "A")

The declaration t1 is just syntactic sugar for a Tuple:

val t1 = Tuple2(1, "A")

There are two ways to access a tuple’s elements. The first way is to access them by the element number:

val tuple3 = (1, "One", "A") // tuple3: (Int, String, String)
tuple3._1 // 1
tuple3._2 // One
tuple3._3 // A

The second way of accessing elements is by using the classical pattern matching in Scala. In this way, we can assign the Tuple elements to some appropriate variable names:

val (num, word, char)  = (1, "One", 'A')
num // 1
word // One
char // A

We can iterate over a Tuple using the productIterator method:

val tuple = (1,2,3,4)
tuple.productIterator.foreach(println)

which would, in this case, output:

1
2
3
4

Technically, Scala 2.x tuples are not collections classes and hence they do not extend the Iterable trait.

5. Conclusion

In this tutorial, we explored the Scala’s collection library. We looked at the differences between mutable and immutable collections and explored the commonly used collections in Scala.

The complete code is available over on GitHub.

Comments are closed on this article!