Scala Collections: List, Vector, Set, and Map
Edited by Dev Kilaru, Senior Data Analyst. How we make our content
Introduction
Scala's collections library is one of its best features. The default collections are immutable: operations return a new collection rather than changing the old one. A small set of methods, such as map, filter, and fold, can express most data processing in a few readable lines. These are the same ideas you will meet again in Apache Spark, whose API deliberately mirrors Scala collections, so time spent here pays off directly in data engineering.
Prerequisites
- Functions and lambdas from the previous lesson
Step-by-Step Guide
Step 1: Create the main collection types
A List is an immutable linked list that is fast to add to at the front. A Vector is an indexed sequence with fast random access and is a good general default. A Set holds unique elements, and a Map holds key-value pairs. All of them are created by calling the type name with its elements, with no new keyword. Because these are immutable, adding an element with the prepend or append operators gives a new collection and leaves the original unchanged.
val nums = List(3, 1, 4, 1, 5)
val vec = Vector("a", "b", "c")
val unique = Set(1, 2, 2, 3) // Set(1, 2, 3)
val ages = Map("Asha" -> 31, "Ben" -> 27)
val more = 0 :: nums // List(0, 3, 1, 4, 1, 5)
val extended = vec :+ "d" // Vector(a, b, c, d)
println(nums) // unchangedStep 2: Transform with map and filter
The map method applies a function to every element and returns a collection of the results, while filter keeps the elements for which a function returns true. They chain naturally from left to right. Neither changes the original, and neither requires a loop or a mutable accumulator. These two methods alone replace a large share of the loops you would otherwise write.
val prices = List(12.5, 8.0, 30.0, 4.25)
val withTax = prices.map(_ * 1.08)
val expensive = prices.filter(_ > 10) // List(12.5, 30.0)
val names = List("asha", "ben", "chen")
names.map(_.capitalize) // List(Asha, Ben, Chen)
names.filter(_.length > 3).map(_.toUpperCase) // List(ASHA, CHEN)Step 3: Aggregate with fold and friends
To reduce a collection to a single value, use sum, min, max, or the general foldLeft, which takes a starting value and a function that combines the running result with each element. foldLeft is the general tool behind many specialized methods. For checks and lookups, exists tells whether any element matches, forall whether all do, and find returns the first match wrapped in an Option, since there might not be one. Prefer these named methods over hand-written loops, because they say what you mean.
val nums = List(3, 1, 4, 1, 5)
nums.sum // 14
nums.foldLeft(0)((acc, n) => acc + n) // 14
nums.max // 5
nums.exists(_ > 4) // true
nums.forall(_ > 0) // true
nums.find(_ > 3) // Some(4)Step 4: Flatten with flatMap and combine with zip
The flatMap method applies a function that returns a collection for each element, then joins all the results into one flat collection, which is useful when each input produces zero or more outputs. The zip method pairs up elements from two collections by position, and zipWithIndex pairs each element with its position. Sorting uses sorted for natural order and sortBy for a key derived from each element. These cover most reshaping tasks without any index arithmetic.
val sentences = List("scala is fun", "spark uses scala")
sentences.flatMap(_.split(" ")) // List(scala, is, fun, spark, uses, scala)
val names = List("Asha", "Ben")
val scores = List(90, 85)
names.zip(scores) // List((Asha,90), (Ben,85))
List("pear", "fig", "banana").sortBy(_.length) // List(fig, pear, banana)Step 5: Group and count
Grouping is a core data task. The groupBy method builds a Map from a key to the list of elements with that key. Scala 2.13 and 3 also provide groupMapReduce, which groups, transforms, and combines in one pass and is the neat way to count occurrences. Here a word-frequency count is a single expression. This group-then-aggregate pattern is exactly what a Spark job does across a cluster, only on far more data.
val words = "the quick fox and the lazy dog and the end".split(" ").toList
val counts = words.groupMapReduce(identity)(_ => 1)(_ + _)
// Map(the -> 3, and -> 2, quick -> 1, ...)
val byLength = words.groupBy(_.length)
println(counts("the")) // 3Step 6: Use for comprehensions and look up Map values safely
A for comprehension with yield builds a new collection and can combine several sources, with guards to filter. Under the hood it calls map, flatMap, and filter, so it works with any type that supports them. For maps, the get method returns an Option because the key may be missing, and getOrElse supplies a default. Writing ages(key) throws an exception when the key is absent, so use it only when the key is guaranteed.
val pairs = for
x <- 1 to 3
y <- List("a", "b")
yield (x, y) // (1,a), (1,b), (2,a), ...
val ages = Map("Asha" -> 31, "Ben" -> 27)
ages.get("Asha") // Some(31)
ages.get("Zed") // None
ages.getOrElse("Zed", 0) // 0
ages.updated("Ben", 28) // a new Map with Ben changedFAQs
- When should I use List and when Vector?
- Use List when you mostly add to the front and process from the start, which suits recursive code. Use Vector when you need fast access by index or appends at the end. If unsure, Vector is a safe general default.
- Are there mutable collections in Scala?
- Yes. The scala.collection.mutable package has ArrayBuffer, mutable.Map, and others. They are useful inside a function that builds a large result efficiently, but prefer immutable collections in the public parts of your code.
- Why does Scala code seem to use map and filter instead of loops?
- These methods describe the transformation directly without managing indexes or temporary variables. The code is shorter, harder to get wrong, and easy to run in parallel, which is why distributed frameworks such as Spark use the same vocabulary.
Summary
You have worked with List, Vector, Set, and Map, and with the methods that do most of the work: map, filter, foldLeft, flatMap, zip, sortBy, and groupMapReduce. Combined with for comprehensions and safe lookups through Option, you can express significant data processing without mutable state. Next you will define your own types with classes, case classes, and traits.
Related tutorials
- Schedule and Debug Shell Scripts: cron, bash -x, and Portability
- Shell Scripting Project: A Backup Script with Rotation and a Log Report
- Writing Safe Bash Scripts: set -euo pipefail, getopts, and trap
- Shell Pipes, Redirection, grep, sed, and awk for Text Processing
- Bash Loops, Arrays, and Functions Explained
Comments
Log in or create a free account to join the discussion.