avatarGabriel Shanahan

Summary

The provided content offers an in-depth exploration of aggregation operations in Kotlin, covering functions like fold, reduce, average, count, sum, min, max, and their variants, as well as the joinToString function for converting collections to strings.

Abstract

The article serves as a comprehensive guide to collection aggregation in Kotlin, detailing the use of fold and its variations, including reduce, for accumulating values with an operation. It explains the nuances between fold and reduce, the latter of which does not require an initial value. The author also discusses statistical operations such as average, count, and sum, and their implementations for different numeric types. Additionally, the article covers functions for finding minimum and maximum values (min, max, minBy, maxBy, minOf, maxOf) and their respective variants that handle empty collections or require a Comparator. The joinToString function is presented as a versatile tool for converting collections into strings with customizable formatting options. The article is part of the "Kotlin Primer" series, aimed at facilitating Kotlin adoption in Java-centric organizations, and includes interactive examples using the Kotlin Playground.

Opinions

  • The author emphasizes the importance of understanding the fundamental aggregation operation fold and its variations, suggesting that these operations are key to effective collection manipulation in Kotlin.
  • The distinction between fold and reduce is highlighted as significant, with the latter being more suitable for scenarios where the accumulated type is compatible with the argument type and no initial value is needed.
  • The article conveys the opinion that the *Indexed variants of aggregation functions, which include the element index in the operation, are valuable for more complex aggregations.
  • The provision of *orNull variants for functions like reduce and min/max is seen as a practical solution for handling empty collections gracefully.
  • The inclusion of joinToString as a method for converting collections to strings reflects the author's view on its utility in everyday programming tasks, particularly for its flexibility in formatting output.
  • By providing a series of interactive examples, the author demonstrates a commitment to practical learning and encourages readers to engage with the Kotlin language actively.
  • The mention of the "Kotlin Primer" series and its origin as an organizational learning resource for Etnetera a.s. suggests the author's belief in the series' effectiveness in promoting Kotlin within established Java-based development environments.

Collection Operations: Aggregators

An introduction to the most important functions for aggregation: fold, reduce, average, count, sum, min, max, and their variants. A quick note on converting collections to strings using joinToString.

— — — — — — — — — — — — — — —

THE CURRENT VERSION OF THIS ARTICLE IS PUBLISHED HERE.

— — — — — — — — — — — — — — —

Tags: #FYI++

This article is part of the Kotlin Primer, an opinionated guide to the Kotlin language, which is indented to help facilitate Kotlin adoption inside Java-centric organizations. It was originally written as an organizational learning resource for Etnetera a.s. and I would like to express my sincere gratitude for their support.

It is recommended to read the Introduction before moving on. Check out the Table of Contents for all articles.

fold

inline fun <T, R> Iterable<T>.fold(
    initial: R,
    operation: (R, T) -> R
): R

The most fundamental aggregation operation is fold. The way it works is simple, but doesn’t lend itself well to a word-based descriptions, so I’ll demonstrate it instead, and then show you one way it could be implemented.

As always, there are many variations of fold. One of them has a seemingly different name, reduce:

inline fun <S, T : S> Iterable<T>.reduce(operation: (S, T) -> S): S

The difference between reduce and fold is that there is no initial element, and the operation accepts and produces types that are related by inheritance. In essence, reduce is fold when the operation produces a type that is compatible with the argument and no initial value is needed.

The first example from above could therefore be rewritten using reduce:

It is key to remember that, since there is no initial value, reduce cannot be used with empty collections, and will throw an UnsupportedOperationException in such a situation. However, there is also a reduceOrNull variant that returns null if run on an empty collection.

Both fold and reduce have *Right variants, where the elements in the successive iterations are taken from the end instead of the beginning. Be careful! The order of the arguments is switched relative to the regular variants.

Another are the *Indexed variants, which, as you’ve come to expect, also send the element index into the operation.

inline fun <T, R> Iterable<T>.runningFoldIndexed(
    initial: R, 
    operation: (Int, R, T) -> R
): List<R>
inline fun <S, T : S> Iterable<T>.reduceIndexed(
    operation: (Int, S, T) -> S
): S

There are also running* variants for fold, foldIndexed, reduce and reduceIndexed (i.e. runningFold etc.). Instead of returning the final value, they instead return a list which includes all the intermediate values from the successive iterations:

An alias for runningFold and runningFoldIndexed are scan and scanIndexed.

Statistics

average

Defined for Iterable<Byte>, Iterable<Double>, Iterable<Float>, Iterable<Int>, Iterable<Long> and Iterable<Short>, returns the average of the list.

count

fun <T> Iterable<T>.count(): Int
fun <T> Iterable<T>.count(predicate: (T) -> Boolean): Int

Returns the number of elements in the list. A more interesting variant accepts a predicate, and returns the number of elements satisfying that predicate.

sum, sumOf

The sum method is defined for Iterable<Byte>, Iterable<Double>, Iterable<Float>, Iterable<Int>, Iterable<Long> and Iterable<Short>, returns the sum of the elements of the list.

The sumOf method is defined for any Iterable<T>, accepts a lambda that transforms a T into a numeric type, and returns the sum of those numeric values. Permissible numeric types are Double, Int, Long, UInt, ULong, BigDecimal and BigInteger.

Both return 0 (of the appropriate type) if the collection is empty.

Example

min, minBy, minOf

fun <T : Comparable<T>> Iterable<T>.min(): T
inline fun <T, R : Comparable<R>> Iterable<T>.minBy(
    selector: (T) -> R
): T
inline fun <T, R : Comparable<R>> Iterable<T>.minOf(
    selector: (T) -> R
): R

Various functions which calculate the minimum of a collection of comparable elements. The difference between minBy and minOf is what gets returned — minBy returns the element of the receiver for which the selector yields the smallest value, while minOf returns the actual value.

Example

All of the above throw NoSuchElementException if used on an empty list. Alternatively, there are *orNull variants which return null for empty lists.

There are also *With variants (e.g. minWith, minOfWith, minOfWithOrNull) which allow you to specify a Comparator for collections of elements which are not comparable.

Example

max, maxBy, maxOf

Exactly the same as their min counterparts, except they operate with largest values instead of smallest ones.

Conversion to String

fun <T> Iterable<T>.joinToString(
    separator: kotlin.CharSequence = ", ",
    prefix: kotlin.CharSequence = "",
    postfix: kotlin.CharSequence = "",
    limit: kotlin.Int = -1,
    truncated: kotlin.CharSequence = "...",
    transform: ((T) -> kotlin.CharSequence)? = null
): kotlin.String

It is often useful to join a whole collection into string, with the ability to specify separators, a pre-/postfix, etc. This is exactly what the joinToString function is for.

The meaning of its various parameters is best demonstrated by example:

Go back to Collection Operations: Predicates, jump to the Table of Contents, or continue to Collection Operations: Grouping.

Kotlin
Java
Programming
Collection
Functional Programming
Recommended from ReadMedium