# Folding Lists
fold()
combines all elements of a list, in order, to generate a single result.
A common exercise is to implement operations such as sum()
or reverse()
using fold()
. Here, fold()
sums a sequence:
// FoldingLists/SumViaFold.kt
import atomictest.eq
fun main() {
val list = listOf(1, 10, 100, 1000)
list.fold(0) { sum, n ->
sum + n
} eq 1111
}
fold()
takes the initial value (its argument, 0
in this case) and successively applies the operation (expressed here as a lambda) to combine the current accumulated value with each element. fold()
first adds 0
(the initial value) and 1
to get 1
. That becomes the sum
, which is then added to the 10
to get 11
, which becomes the new sum
. The operation is repeated for two more elements: 100
and 1000
. This produces 111
and 1111
. The fold()
will stop when there is nothing else in the list, returning the final sum
of 1111
. Of course, fold()
doesn’t really know it’s doing a “sum”—the choice of identifier name was ours, to make it easier to understand.
To illuminate the steps in a fold()
, here’s SumViaFold.kt
using an ordinary for
loop:
// FoldingLists/FoldVsForLoop.kt
import atomictest.eq
fun main() {
val list = listOf(1, 10, 100, 1000)
var accumulator = 0
val operation =
{ sum: Int, i: Int -> sum + i }
for (i in list) {
accumulator = operation(accumulator, i)
}
accumulator eq 1111
}
fold()
accumulates values by successively applying operation
to combine the current element with the accumulator value.
Although fold()
is an important concept and the only way to accumulate values in pure functional languages, you may sometimes still use an ordinary for
loop in Kotlin.
foldRight()
processes elements starting from right to left, as opposed to fold()
which processes the elements from left to right. This example demonstrates the difference:
// FoldingLists/FoldRight.kt
import atomictest.eq
fun main() {
val list = listOf('a', 'b', 'c', 'd')
list.fold("*") { acc, elem ->
"($acc) + $elem"
} eq "((((*) + a) + b) + c) + d"
list.foldRight("*") { elem, acc ->
"$elem + ($acc)"
} eq "a + (b + (c + (d + (*))))"
}
fold()
first applies the operation to a
, as we can see in (*) + a
, while foldRight()
first processes the right-hand element d
, and processes a
last.
fold()
and foldRight()
take an explicit accumulator value as the first argument. Sometimes the first element can act as an initial value. reduce()
and reduceRight()
behave like fold()
and foldRight()
but use the first and last element, respectively, as the initial value:
// FoldingLists/ReduceAndReduceRight.kt
import atomictest.eq
fun main() {
val chars = "A B C D E F G H I".split(" ")
chars.fold("X") { a, e -> "$a $e"} eq
"X A B C D E F G H I"
chars.foldRight("X") { a, e -> "$a $e" } eq
"A B C D E F G H I X"
chars.reduce { a, e -> "$a $e" } eq
"A B C D E F G H I"
chars.reduceRight { a, e -> "$a $e" } eq
"A B C D E F G H I"
}
runningFold()
and runningReduce()
produce a List
containing all the intermediate steps of the process. The final value in the List
is the result of the fold()
or reduce()
:
// FoldingLists/RunningFold.kt
import atomictest.eq
fun main() {
val list = listOf(11, 13, 17, 19)
list.fold(7) { sum, n ->
sum + n
} eq 67
list.runningFold(7) { sum, n ->
sum + n
} eq "[7, 18, 31, 48, 67]"
list.reduce { sum, n ->
sum + n
} eq 60
list.runningReduce { sum, n ->
sum + n
} eq "[11, 24, 41, 60]"
}
runningFold()
first stores the initial value (7
), then stores each intermediate result. runningReduce()
keeps track of each sum
value.
Exercises and solutions can be found at www.AtomicKotlin.com.