Перейти к основному содержимому

Операции агрегирования

Операция агрегирования превращает всю коллекцию в одно значение. Примеры: количество элементов, их сумма, самый большой элемент или все элементы, соединённые в один результат.

Некоторые из них Go даёт сразу. len() считает элементы. slices.Min() и slices.Max() из пакета slices находят самый маленький и самый большой элемент. Для суммы или среднего встроенной функции нет: элементы складывают в цикле for.

Будьте внимательны: slices.Min() и slices.Max() вызывают панику (программа аварийно завершается) на пустом slice. Сначала проверяйте len(s) > 0, если slice может быть пустым.

Во многих языках есть методы fold и reduce, которые соединяют элементы один за другим. В Go таких методов у slice нет. Вместо этого вы один раз пишете небольшой обобщённый helper и потом используете его повторно. Helper Fold ниже работает со slice любого типа.

Синтаксис обобщённого helper Fold

T — это тип элемента, а R — тип результата. acc — это аккумулятор: он начинается со значения initial, а f по очереди соединяет его с каждым элементом. Последнее значение acc — это результат.

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
}

Основные агрегаты

Самые частые агрегаты — это количество, минимум, максимум, сумма и среднее.

  • len(numbers) даёт количество.
  • slices.Min(numbers) и slices.Max(numbers) дают самый маленький и самый большой элемент. Они работают с числами и со строками.
  • Сумма — это цикл, который прибавляет каждый элемент к переменной. Среднее — это сумма, делённая на количество. Сначала приведите оба значения к float64, иначе деление отбросит дробную часть.

Иногда самый маленький или самый большой элемент нужно выбрать по правилу, а не по самому значению. slices.MinFunc() и slices.MaxFunc() принимают функцию сравнения. Функция получает два элемента и возвращает отрицательное число, ноль или положительное число. cmp.Compare() из пакета cmp напишет это за вас.

Иногда каждый элемент нужно сначала преобразовать функцией, а результаты сложить. Встроенной функции для этого в Go нет, поэтому в примере используется небольшой обобщённый helper SumOf.

Количество, минимум, максимум, сумма и среднее

Для чисел 6, 42, 10 и 4 выведите количество с помощью len(), самый большой и самый маленький элемент с помощью slices.Max() и slices.Min(), а сумму — из цикла. Среднее равно 62 / 4 = 15.5.

Prompt
Возьмите числа 6, 42, 10 и 4. Выведите количество, наибольшее, наименьшее, среднее и сумму, каждое на своей строке с подписью, например «Количество:». Для наибольшего и наименьшего используйте slices.Max и slices.Min, а для суммы — цикл.
Вывод: программа на Go и захваченный вывод
Count, min, max, sum and average

Минимум и максимум по правилу

slices.MinFunc() сравнивает остатки a%3 и b%3. Остатки чисел 5, 42, 10 и 4 равны 2, 0, 1 и 1, поэтому побеждает 42. slices.MaxFunc() сравнивает длины строк и находит "three".

Prompt
Возьмите числа 5, 42, 10 и 4 и выведите элемент с наименьшим остатком от деления на 3, используя slices.MinFunc. Затем возьмите слова one, two, three, four и выведите самое длинное из них, используя slices.MaxFunc со сравнением по длине.
Вывод: программа на Go и захваченный вывод
Min and max by a rule

Сумма функции от каждого элемента

SumOf применяет функцию к каждому элементу и складывает результаты. Удвоение 5, 42, 10 и 4 даёт 122. Деление их пополам как float даёт 30.5.

Prompt
Напишите обобщённый helper SumOf, который с помощью функции превращает каждый элемент slice в число и складывает результаты. Для чисел 5, 42, 10 и 4 выведите сумму каждого элемента, умноженного на 2, а затем сумму каждого элемента, делённого на 2, как float.
Вывод: программа на Go и захваченный вывод
Sum of a function of each element

Fold и Reduce

Fold и Reduce шаг за шагом соединяют все элементы в одно значение. Они хранят текущее значение, которое называется аккумулятор. Для каждого элемента они вызывают функцию с аккумулятором и элементом, а функция возвращает новый аккумулятор.

Разница — в начальном значении:

  • Fold начинает с начального значения, которое вы ему передаёте. Функция выполняется один раз для каждого элемента.
  • Reduce начинает с первого элемента. Функция выполняется начиная со второго элемента, поэтому первый элемент через неё никогда не проходит.

Это важно, когда функция изменяет элемент. Если она удваивает каждый элемент, Reduce оставляет первый элемент без изменений.

В Go нет ни встроенного fold, ни встроенного reduce. Оба — это небольшие обобщённые helper с циклом for внутри. Чтобы свернуть справа, цикл просто начинается с последнего индекса и идёт вниз до 0.

Reduce

Reduce берёт 5 за начало и прибавляет к нему 2, 10 и 4. Результат — 21.

Prompt
Напишите обобщённый helper Reduce, который начинает с первого элемента slice и с помощью функции соединяет с ним остальные элементы. Используйте его, чтобы сложить числа 5, 2, 10 и 4, и выведите результат.
Вывод: программа на Go и захваченный вывод
Reduce

Fold

Fold начинает с 0 и прибавляет каждый элемент, умноженный на 2. Каждый элемент удваивается: 10 + 4 + 20 + 8 = 42.

Prompt
Напишите обобщённый helper Fold, который начинает с начального значения и с помощью функции соединяет с ним каждый элемент slice. Примените его к числам 5, 2, 10 и 4 с начальным значением 0, прибавляя каждый элемент, умноженный на 2, и выведите результат.
Вывод: программа на Go и захваченный вывод
Fold

Fold против Reduce

Оба helper используют одну и ту же функцию: сумма плюс элемент, умноженный на 2. Reduce даёт 37, потому что первый элемент 5 является началом и не удваивается. Fold с начальным значением 0 даёт 42.

Prompt
Примените helper Reduce и helper Fold к числам 5, 2, 10 и 4 с одной и той же операцией: сумма плюс элемент, умноженный на 2. Выведите результат Reduce и результат Fold с подписями, чтобы показать, что Reduce никогда не удваивает первый элемент.
Вывод: программа на Go и захваченный вывод
Fold versus Reduce

Свёртка справа

FoldRight идёт по slice от последнего элемента к первому. Для сложения порядок не важен, поэтому результат снова 42.

Prompt
Напишите helper FoldRight, который идёт по slice от последнего элемента к первому, начиная с начального значения. Примените его к числам 5, 2, 10 и 4 с начальным значением 0, прибавляя каждый элемент, умноженный на 2, и выведите результат.
Вывод: программа на Go и захваченный вывод
Fold from the right

Fold с индексом

Иногда важна позиция элемента, а не только его значение.

В Go обычный цикл уже даёт вам индекс. for idx, element := range numbers идёт с начала. for idx := len(numbers) - 1; idx >= 0; idx-- идёт с конца. Внутри цикла if проверяет индекс и решает, прибавлять ли элемент.

Fold с индексом

Складываются только элементы с чётными индексами: в индексе 0 находится 5, а в индексе 2 — 10, поэтому сумма равна 15.

Prompt
Пройдите по числам 5, 2, 10 и 4 с помощью range, чтобы получить индекс и значение. Сложите только те элементы, у которых индекс чётный, и выведите сумму.
Вывод: программа на Go и захваченный вывод
Fold with index

Fold с индексом справа

То же правило, но цикл начинается с индекса 3 и идёт вниз до 0. Результат всё тот же — 15.

Prompt
Пройдите по числам 5, 2, 10 и 4 от последнего индекса до первого. Сложите только те элементы, у которых индекс чётный, и выведите сумму.
Вывод: программа на Go и захваченный вывод
Fold with index from the right

Безопасный Reduce

Reduce нужен первый элемент, чтобы с него начать. На пустом slice items[0] вызывает панику, и программа аварийно завершается.

В Go для чисел нет null. Способ Go — возвращать два значения: результат и bool с именем ok. Когда slice пустой, ok равен false, а результат — это нулевое значение типа, например 0 для int. Это тот же шаблон value, ok, который вы знаете по map.

Reduce со значением ok

ReduceOK возвращает 21 true для чисел 5, 2, 10 и 4 и 0 false для пустого slice.

Prompt
Напишите обобщённый helper ReduceOK, который возвращает свёрнутое значение и bool. Для пустого slice он возвращает false вместо паники. Сложите числа 5, 2, 10 и 4 и выведите значение и bool, а затем сделайте то же самое с пустым slice.
Вывод: программа на Go и захваченный вывод
Reduce with an ok value

Накопительные Fold и Reduce

Накопительный fold или reduce не выбрасывает промежуточные шаги. Он сохраняет аккумулятор после каждого элемента, поэтому результат — это slice со всеми промежуточными значениями.

В Go вы держите переменную с суммой и внутри цикла добавляете её в результирующий slice с помощью append(). У накопительного reduce результат начинается с первого элемента. У накопительного fold результат начинается с начального значения.

Накопительный reduce

Числа от 0 до 5 складываются одно за другим. Результирующий slice содержит каждую промежуточную сумму: [0 1 3 6 10 15].

Prompt
Возьмите числа 0, 1, 2, 3, 4 и 5. Постройте slice накопленных сумм: начните с первого элемента и после каждого следующего элемента добавляйте сумму. Выведите slice.
Вывод: программа на Go и захваченный вывод
Running reduce

Накопительный fold

То же самое с начальным значением 10. Результат начинается с самой десятки и содержит на один элемент больше, чем входные данные: [10 10 11 13 16 20 25].

Prompt
Возьмите числа 0, 1, 2, 3, 4 и 5. Постройте slice накопленных сумм, который начинается с начального значения 10 и после каждого элемента добавляет сумму. Выведите slice.
Вывод: программа на Go и захваченный вывод
Running fold