1. Introduction
When working with functional programming languages like Scala, it’s common to have a variety of high-order functions to process collections, including reduceLeft, reduceRight, foldLeft, foldRight, scanLeft, and scanRight. Each of these functions combines the elements of a collection into a final result. However, they differ in how they iterate through the collection and how they compile the final result.
In this quick tutorial, we’ll learn about these six important functions of Scala. We will compare and contrast their different behaviors and provide some code examples to illustrate their use cases.
2. reduceLeft and reduceRight
The reduceLeft combines the elements of a collection by successively applying a binary operator from left to right and produces a single result. It applies the binary operation to the first two elements, then applies the outcome to the third element, and so on. It reduces the collection to a single value:
"reduceLeft" should "calculate the sum correctly" in {
val numbers = List(1, 2, 3, 4, 5)
val actualSum = numbers.reduceLeft(_ + _)
// op: 1 + 2 = 3
// op: 3 + 3 = 6
// op: 6 + 4 = 10
// op: 10 + 5 = 15
// res: Int = 15
assert(15 == actualSum)
}
Similarly, we can find the largest number or concatenate a List of Strings:
"reduceLeft" should "find the largest number correctly" in {
val numbers = List(10, 22, 5, 71, 43)
val actualResult = numbers.reduceLeft(_ max _)
assert(71 == actualResult)
}
"reduceLeft" should "concatenate strings correctly" in {
val alphabets = List("A", "B", "C", "D", "E")
val actualResult = alphabets.reduceLeft(_ + _)
assert("ABCDE" == actualResult)
}
The reduceRight function operates in the same manner as reduceLeft, with the only difference being the direction. The reduceRight will apply the binary operation to the last two elements of the collection first, then the third-to-last two elements, and so on, until it reaches the first two elements:
"reduceRight" should "calculate the sum correctly" in {
val numbers = List(1, 2, 3, 4, 5)
val actualSum = numbers.reduceRight(_ + _)
// op: 5 + 4 = 9
// op: 9 + 3 = 12
// op: 12 + 2 = 14
// op: 14 + 1 = 15
// res: Int = 15
assert(15 == actualSum)
}
When the input collection is empty, reduceLeft and reduceRight throw an UnsupportedOperationException because there is nothing to reduce.
Here’s some code that illustrates the behavior:
"reduceLeft" should "throw an exception" in {
val numbers = List.empty[Int]
assertThrows[UnsupportedOperationException] {
numbers.reduceLeft(_ max _)
}
}
3. foldLeft and foldRight
The foldLeft and foldRight are similar to reduceLeft and reduceRight, respectively, but they take an initial value as a parameter.
In the foldLeft, the initial value will combine with the first element of the collection using a binary operator, and the result will combine with the second element, and so on:
"foldLeft" should "calculate the sum correctly" in {
val numbers = List(1, 2, 3, 4, 5)
val actualSum = numbers.foldLeft(5)(_ + _)
// op: 5 + 1 = 6
// op: 6 + 2 = 8
// op: 8 + 3 = 11
// op: 11 + 4 = 15
// op: 15 + 5 = 20
// res: Int = 20
assert(20 == actualSum)
}
Similarly, in the foldRight, the initial value will combine with the last element, and the result will combine with the second-last element, and so on, until it reaches the first element:
"foldRight" should "concatenate the strings correctly" in {
val alphabets = List("A", "B", "C", "D", "E")
val actualResult = alphabets.foldRight("$")(_ + _)
// op: E + $ = E$
// op: D + E$ = DE$
// op: C + DE$ = CDE$
// op: B + CDE$ = BCDE$
// op: A + BCDE$ = ABCDE$
// res: String = ABCDE$
assert("ABCDE$" == actualResult)
}
When the input collection is empty, the foldLeft and foldRight functions return a collection with only the initial element:
"foldRight" should "return the initial element i.e $" in {
val alphabets = List.empty[String]
val actualResult = alphabets.foldRight("$")(_ + _)
assert("$" == actualResult)
}
4. scanLeft and scanRight
The scanLeft and scanRight are similar to foldLeft and foldRight, respectively, but they return a collection of intermediate results, not just the final result**:
"scanLeft" should "have correct intermediate states" in {
val numbers = List(1, 2, 3, 4, 5)
val actualResult = numbers.scanLeft(1)(_ + _)
assert(List(1, 2, 4, 7, 11, 16) == actualResult)
}
"scanRight" should "have correct intermediate states" in {
val numbers = List(1, 2, 3, 4, 5)
val actualResult = numbers.scanRight(1)(_ + _)
assert(List(16, 15, 13, 10, 6, 1) == actualResult)
}
When the input collection is empty, the scanLeft and scanRight functions return a collection with only the initial element:
"scanRight" should "return the initial element i.e 5" in {
val numbers = List.empty[Int]
val actualResult = numbers.scanRight(5)(_ + _)
assert(List(5) == actualResult)
}
5. When to Use Each
What function should be chosen over the other totally depends on the context. We can choose the ideal function for our needs by understanding their distinct behaviors.
Therefore, the following table can help us to find the most suitable function according to our requirements:
Function
Output a Single Value
Output Intermediate Results
Accepts Initial Value
When Collection is Empty
Examples
reduceLeft
Yes
No
No
throws UnsupportedOperationException
Find max/min in the list
reduceRight
Yes
No
No
throws UnsupportedOperationException
String concatenation
foldLeft
Yes
No
Yes
returns a collection with only the initial element
Tree traversal
foldRight
Yes
No
Yes
returns a collection with only the initial element
File system traversal
scanLeft
No
Yes
Yes
returns a collection with only the initial element
Inventory management
scanRight
No
Yes
Yes
returns a collection with only the initial element
Statistical analysis
6. Conclusion
In this tutorial, we have learned about the six powerful functions of Scala language for aggregating the elements of a collection. These functions use different traversal orders and accumulation strategies to aggregate elements. By understanding the differences between these functions, we can choose the right one for our needs.
As usual, the full source code can be found over on GitHub.