Skip to main content

Aggregate Operations

An aggregate operation turns a whole collection into one value. Examples are the number of elements, their sum, the biggest element, or all elements combined into one result.

Go gives you some of these directly. len() counts the elements. slices.Min() and slices.Max() from the slices package find the smallest and the biggest element. For a sum or an average there is no built-in function: you add the elements up in a for loop.

Be careful: slices.Min() and slices.Max() panic (crash the program) on an empty slice. Check len(s) > 0 first when the slice might be empty.

Many languages have fold and reduce methods for combining elements one by one. Go has no such methods on slices. Instead you write a small generic helper once and reuse it. The Fold helper below works for a slice of any type.

Generic Fold helper syntax

T is the element type and R is the result type. acc is the accumulator: it starts as initial, and f combines it with every element in turn. The last value of acc is the result.

main.go
func Fold[T, R any](items []T, initial R, f func(R, T) R) R {
  acc := initial
  for _, v := range items {
    acc = f(acc, v)
  }
  return acc
}

Basic Aggregates

The most common aggregates are count, minimum, maximum, sum and average.

  • len(numbers) gives the count.
  • slices.Min(numbers) and slices.Max(numbers) give the smallest and the biggest element. They work for numbers and strings.
  • The sum is a loop that adds every element to a variable. The average is the sum divided by the count. Convert both to float64 first, or the division throws away the decimal part.

Sometimes the smallest or biggest element should be picked by a rule, not by the value itself. slices.MinFunc() and slices.MaxFunc() take a compare function. The function gets two elements and returns a negative number, zero or a positive number. cmp.Compare() from the cmp package writes that for you.

Sometimes every element must be converted with a function first and the results added up. Go has no built-in function for that, so the example uses a small generic SumOf helper.

Count, min, max, sum and average

For the numbers 6, 42, 10 and 4 print the count with len(), the biggest and smallest element with slices.Max() and slices.Min(), and the sum from a loop. The average is 62 / 4 = 15.5.

Prompt
Take the numbers 6, 42, 10 and 4. Print the count, the largest, the smallest, the average and the sum, each on its own line with a label such as "Count:". Use slices.Max and slices.Min for the largest and smallest, and a loop for the sum.
Output: Go program and captured output
Count, min, max, sum and average

Min and max by a rule

slices.MinFunc() compares the remainders a%3 and b%3. The remainders of 5, 42, 10 and 4 are 2, 0, 1 and 1, so 42 wins. slices.MaxFunc() compares string lengths and finds "three".

Prompt
Take the numbers 5, 42, 10 and 4 and print the element with the smallest remainder after dividing by 3, using slices.MinFunc. Then take the words one, two, three, four and print the longest one using slices.MaxFunc with a length comparison.
Output: Go program and captured output
Min and max by a rule

Sum of a function of each element

SumOf applies the function to every element and adds the results. Doubling 5, 42, 10 and 4 gives 122. Halving them as floats gives 30.5.

Prompt
Write a generic SumOf helper that turns every element of a slice into a number with a function and adds the results up. For the numbers 5, 42, 10 and 4 print the sum of each element times 2, and then the sum of each element divided by 2 as a float.
Output: Go program and captured output
Sum of a function of each element

Fold and Reduce

Fold and Reduce combine all elements into one value, step by step. They keep a running value called the accumulator. For each element they call a function with the accumulator and the element, and the function returns the new accumulator.

The difference is the starting value:

  • Fold starts with an initial value that you give it. The function runs once for every element.
  • Reduce starts with the first element. The function runs for the second element onward, so the first element is never passed through it.

This matters when the function changes the element. If it doubles every element, Reduce leaves the first element as it is.

Go has neither fold nor reduce built in. Both are small generic helpers with a for loop inside. To fold from the right, the loop simply starts at the last index and goes down to 0.

Reduce

Reduce takes 5 as the start and adds 2, 10 and 4 to it. The result is 21.

Prompt
Write a generic Reduce helper that starts with the first element of a slice and combines the remaining elements into it with a function. Use it to add up the numbers 5, 2, 10 and 4 and print the result.
Output: Go program and captured output
Reduce

Fold

Fold starts with 0 and adds each element times 2. Every element is doubled: 10 + 4 + 20 + 8 = 42.

Prompt
Write a generic Fold helper that starts with an initial value and combines every element of a slice into it with a function. Use it on the numbers 5, 2, 10 and 4 with initial value 0, adding each element times 2, and print the result.
Output: Go program and captured output
Fold

Fold versus Reduce

Both helpers use the same function, sum plus element times 2. Reduce gives 37 because the first element 5 is the start and is not doubled. Fold with initial value 0 gives 42.

Prompt
Use both a Reduce helper and a Fold helper on the numbers 5, 2, 10 and 4 with the same operation: sum plus element times 2. Print the Reduce result and the Fold result with labels, to show that Reduce never doubles the first element.
Output: Go program and captured output
Fold versus Reduce

Fold from the right

FoldRight walks the slice from the last element to the first. Addition does not care about the order, so the result is again 42.

Prompt
Write a FoldRight helper that walks a slice from the last element to the first, starting with an initial value. Use it on the numbers 5, 2, 10 and 4 with initial value 0, adding each element times 2, and print the result.
Output: Go program and captured output
Fold from the right

Fold with Index

Sometimes the position of an element matters, not only its value.

In Go a plain loop already gives you the index. for idx, element := range numbers walks from the start. for idx := len(numbers) - 1; idx >= 0; idx-- walks from the end. Inside the loop an if checks the index and decides whether to add the element.

Fold with index

Only the elements at even indexes are added: index 0 holds 5 and index 2 holds 10, so the sum is 15.

Prompt
Loop over the numbers 5, 2, 10 and 4 with range so you get the index and the value. Add up only the elements whose index is even and print the sum.
Output: Go program and captured output
Fold with index

Fold with index from the right

The same rule, but the loop starts at index 3 and goes down to 0. The result is still 15.

Prompt
Loop over the numbers 5, 2, 10 and 4 from the last index down to the first. Add up only the elements whose index is even and print the sum.
Output: Go program and captured output
Fold with index from the right

Safe Reduce

Reduce needs a first element to start with. On an empty slice items[0] panics and the program crashes.

Go has no null for numbers. The Go way is to return two values: the result and a bool named ok. When the slice is empty, ok is false and the result is the zero value of the type, for example 0 for int. This is the same value, ok pattern you know from maps.

Reduce with an ok value

ReduceOK returns 21 true for the numbers 5, 2, 10 and 4, and 0 false for an empty slice.

Prompt
Write a generic ReduceOK helper that returns the reduced value and a bool. It returns false for an empty slice instead of panicking. Add up the numbers 5, 2, 10 and 4 and print the value and the bool, then do the same with an empty slice.
Output: Go program and captured output
Reduce with an ok value

Running Fold and Reduce

A running fold or reduce does not throw away the steps in between. It saves the accumulator after every element, so the result is a slice of all the running values.

In Go you keep a sum variable and append() it to a result slice inside the loop. For a running reduce the result starts with the first element. For a running fold the result starts with the initial value.

Running reduce

The numbers 0 to 5 are added one by one. The result slice holds every sum along the way: [0 1 3 6 10 15].

Prompt
Take the numbers 0, 1, 2, 3, 4 and 5. Build a slice of running sums: start with the first element and append the sum after every further element. Print the slice.
Output: Go program and captured output
Running reduce

Running fold

The same with the initial value 10. The result begins with 10 itself and has one more element than the input: [10 10 11 13 16 20 25].

Prompt
Take the numbers 0, 1, 2, 3, 4 and 5. Build a slice of running sums that starts with the initial value 10 and appends the sum after every element. Print the slice.
Output: Go program and captured output
Running fold