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

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

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

Массивы дают часть этого напрямую. numbers.length считает элементы. Math.max() и Math.min() находят наибольшее и наименьшее число; элементы передаются им так: Math.max(...numbers). Для суммы или среднего метода нет: элементы объединяются с помощью reduce().

Будьте внимательны с пустыми массивами. Math.max() без значений возвращает -Infinity, а Math.min() возвращает Infinity, поэтому проверяйте numbers.length > 0, если массив может оказаться пустым.

Во многих языках есть отдельные методы fold и reduce для пошагового объединения элементов. Здесь это один и тот же метод, reduce(), и разница только в том, передаёте ли вы начальное значение. reduceRight() делает тот же проход, но от последнего элемента к первому.


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

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

  • numbers.length даёт количество.
  • Math.max(...numbers) и Math.min(...numbers) дают наибольшее и наименьшее число. Они работают только с числами.
  • Сумма — это reduce(), который прибавляет каждый элемент к накопленному итогу. Среднее — это сумма, делённая на количество; деление сохраняет дробную часть, поэтому 62 / 4 равно 15.5.

Иногда наименьший или наибольший элемент нужно выбирать по правилу, а не по самому значению. reduce() умеет и это: на каждом шаге вы оставляете лучший из двух элементов. Это работает и для слов, и для объектов, где Math.min() уже не поможет.

Иногда каждый элемент сначала нужно превратить в число, а результаты сложить. Это тоже один reduce(): прибавляйте вычисленное значение вместо самого элемента.

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

Для чисел 6, 42, 10 и 4 программа выводит количество через length, наибольшее и наименьшее через Math.max() и Math.min(), а сумму — через reduce(). Среднее равно 62 / 4 = 15.5.

Prompt
Напишите программу на TypeScript, которая берёт числа 6, 42, 10 и 4 и выводит количество, наибольшее, наименьшее, среднее и сумму — каждое на своей строке с подписью вроде «Количество:». Используйте length, Math.max, Math.min и reduce() для суммы.
Вывод: программа на TypeScript и захваченный вывод
Count, min, max, sum and average

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

Первый reduce() оставляет элемент с меньшим остатком от деления на 3. Остатки чисел 5, 42, 10 и 4 равны 2, 0, 1 и 1, поэтому побеждает 42. Второй оставляет более длинное слово и находит "three".

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

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

Колбэк прибавляет к итогу вычисленное значение вместо самого элемента. Удвоение чисел 5, 42, 10 и 4 даёт 122. Деление их пополам даёт 30.5, потому что деление сохраняет дробную часть.

Prompt
Напишите программу на TypeScript, которая берёт числа 5, 42, 10 и 4 и с помощью reduce() с начальным значением 0 выводит сумму каждого элемента, умноженного на 2, а затем сумму каждого элемента, делённого на 2.
Вывод: программа на TypeScript и захваченный вывод
Sum of a function of each element

Fold и reduce

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

Здесь это один и тот же метод. Разница — во втором аргументе reduce():

  • reduce(fn, initial) — это fold. Он начинает с начального значения, которое вы передали, поэтому функция выполняется для каждого элемента.
  • reduce(fn) — это reduce. Он начинает с первого элемента, поэтому функция работает начиная со второго элемента, а первый элемент через неё никогда не проходит.

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

Это важно и для пустых массивов. reduce(fn, initial) просто возвращает начальное значение, а у reduce(fn) нет ничего, с чего начать, и он выбрасывает TypeError.

Чтобы объединять с другого конца, используйте reduceRight(): тот же метод, но проход идёт от последнего элемента к первому.

Reduce

reduce() без начального значения берёт 5 как старт и прибавляет к нему 2, 10 и 4, поэтому результат равен 21. Тот же вызов на пустом массиве выбрасывает ошибку, и программа выводит сообщение, которое она несёт: Reduce of empty array with no initial value.

Prompt
Напишите программу на TypeScript, которая складывает числа 5, 2, 10 и 4 с помощью reduce() без начального значения, так что стартом становится первый элемент, и выводит результат. Затем вызовите тот же reduce() для пустого массива внутри try/catch и выведите сообщение об ошибке.
Вывод: программа на TypeScript и захваченный вывод
Reduce

Fold

Здесь reduce() получает начальное значение 0 и прибавляет каждый элемент, умноженный на 2. Удваивается каждый элемент: 10 + 4 + 20 + 8 = 42.

Prompt
Напишите программу на TypeScript, которая использует reduce() с начальным значением 0 для чисел 5, 2, 10 и 4, прибавляя каждый элемент, умноженный на 2, и выводит результат.
Вывод: программа на TypeScript и захваченный вывод
Fold

Fold против reduce

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

Prompt
Напишите программу на TypeScript, которая дважды применяет одну и ту же функцию — сумма плюс элемент, умноженный на 2 — к числам 5, 2, 10 и 4: один раз через reduce() без начального значения, другой раз с начальным значением 0. Выведите оба результата с подписями «Reduce:» и «Fold:», чтобы показать, что без начального значения первый элемент никогда не удваивается.
Вывод: программа на TypeScript и захваченный вывод
Fold versus Reduce

Fold справа

reduceRight() проходит массив от последнего элемента к первому. Сложению порядок не важен, поэтому результат снова равен 42.

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

Fold с индексом

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

Колбэк reduce() получает индекс третьим аргументом, после аккумулятора и элемента. Условие на этот индекс затем решает, что делать с элементом. reduceRight() передаёт те же индексы; он лишь проходит их с конца.

Fold с индексом

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

Prompt
Напишите программу на TypeScript, которая с помощью reduce() проходит по числам 5, 2, 10 и 4 и складывает только те элементы, у которых индекс чётный, используя аргумент индекса в колбэке. Выведите сумму.
Вывод: программа на TypeScript и захваченный вывод
Fold with index

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

То же правило с reduceRight(). Колбэк видит индекс 3 первым, а индекс 0 последним, и результат по-прежнему равен 15.

Prompt
Напишите программу на TypeScript, которая с помощью reduceRight() проходит по числам 5, 2, 10 и 4 и складывает только те элементы, у которых индекс чётный. Выведите сумму.
Вывод: программа на TypeScript и захваченный вывод
Fold with index from the right

Безопасный reduce

reduce() без начального значения нуждается в первом элементе, с которого можно начать. На пустом массиве он выбрасывает TypeError и останавливает программу.

Один выход — начальное значение. Другой — честно сказать «результата нет»: сначала проверить длину и вернуть undefined. Тип возвращаемого значения тогда — number | undefined, что заставляет вызывающий код подумать о случае пустого массива, а ?? за один шаг подставляет запасное значение вместо undefined.

Reduce, который может вернуть undefined

sumOrUndefined() возвращает 21 для чисел 5, 2, 10 и 4 и undefined для пустого массива. sumOrUndefined(empty) ?? 0 превращает этот undefined в 0.

Prompt
Напишите программу на TypeScript с функцией sumOrUndefined, которая возвращает number | undefined: для пустого массива она возвращает undefined вместо выбрасывания ошибки, а иначе — сумму из reduce(). Выведите результат для чисел 5, 2, 10 и 4, затем для пустого массива, а затем случай пустого массива с ?? 0 в качестве запасного значения.
Вывод: программа на TypeScript и захваченный вывод
Reduce that can return undefined

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

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

Метода для этого нет. Вы храните переменную с суммой и внутри цикла добавляете её в массив результата через push(). Для накопительного reduce результат начинается с первого элемента. Для накопительного fold он начинается с начального значения, поэтому содержит на один элемент больше, чем исходный массив.

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

Числа от 0 до 5 складываются одно за другим. Массив результата хранит каждую промежуточную сумму и выводится на одной строке с помощью join(' '): 0 1 3 6 10 15.

Prompt
Напишите программу на TypeScript, которая берёт числа 0, 1, 2, 3, 4 и 5 и строит массив накопленных сумм: начните с первого элемента и добавляйте новую сумму после каждого следующего элемента. Выведите массив на одной строке с помощью join(' ').
Вывод: программа на TypeScript и захваченный вывод
Running reduce

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

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

Prompt
Напишите программу на TypeScript, которая берёт числа 0, 1, 2, 3, 4 и 5 и строит массив накопленных сумм, начинающийся с начального значения 10, добавляя новую сумму после каждого элемента. Выведите массив на одной строке с помощью join(' ').
Вывод: программа на TypeScript и захваченный вывод
Running fold