Collection processing in Kotlin: Folding and reducing
This is a chapter from the book Functional Kotlin. You can find it on LeanPub or Amazon. It is also available as a course.
fold
is the most universal method in our collection processing toolbox. We use it rarely because Kotlin standard library has already provided most important aggregate operations for us, but if we are missing a method for a specific task, fold
is at our service.
Let's see it practice. fold
is a method that accumulates all elements into a single variable (called an "accumulator") using a defined operation. For instance, let's say that our collection contains the numbers from 1 to 4, our initial accumulator value is 0, and our operation is addition. So fold
will:
- add the first value 1 to the initial accumulator value 0,
- then it will add the result 1 to the next value 2,
- then it will add the result 3 to the next value 3,
- then it will add the result 6 to the next value 4,
- and the result is 10.
As you can see, fold(0) { acc, i -> acc + i }
calculates the sum of all the numbers.
Since you can specify the initial value, you can decide the result type. If your initial value is an empty string and your operation is addition, then the result will be a "1234" string.
fold
is very universal. Nearly all collection processing methods can be implemented using it.
On the other hand, thanks to the fact that the Kotlin standard library has so many collection processing functions, we rarely need to use fold
. Even the functions we presented before that calculate a sum and join elements into a string have dedicated methods.
There is currently no standard library method to calculate the product of all the numbers in a collection, so this is where fold
can be used. We might use it directly, or we might use it to implement the product
method ourselves.
If you want to reverse the order of accumulation (to start from the end of the collection), use
foldRight
.
In some situations, you might want to have not only the result of fold
accumulations but also all the intermediate values. For that, you can use the runningFold
method or its alias1 scan
.
runningFold(init, oper).last()
andscan(init, oper).last()
always give the same result asfold(init, oper)
.
reduce
reduce
is a very similar function to fold
: it also accumulates all elements using a defined transformation. The difference is that in reduce
we do not define the initial value, and so reduce
uses the first element as the initial value. There are two consequences of this fact:
- If a collection is empty,
reduce
throws an exception. If we are not certain that a collection has elements, we should usereduceOrNull
, which returnsnull
for an empty collection. - The result from
reduce
must be of the same type as its elements. reduce
is slightly faster thanfold
because it has one operation less to do.
list.reduce(oper)
is a lot likelist.drop(1).fold(list[0], oper)
.
In general, I prefer using fold
whenever there is a "zero" value because fold
does not face the risk of an empty collection and it is able to control the result type.
Just like for
fold
, there isrunningReduce
andreduceRight
.
sum
I mentioned that there is already a function to calculate the sum of all the numbers in a collection, and its name is sum
. It is implemented for all the basic ways of representing numbers, like Int
, Long
, Double
, etc.
When you have a list of elements and you want to calculate the sum of one of their properties, you could first map the elements onto the values of these properties, but it is more efficient to use sumOf
, which extracts a countable value for each element and then sums these values.
In this chapter, by aliases we will mean functions with exactly the same meaning.