skip to main content

Array.reduce

js

Кратко 🔗

Метод массива reduce позволяет превратить массив в любое другое значение с помощью переданной функции-колбэка и начального значения. Функция-колбэк будет вызвана для каждого элемента массива и всегда должна возвращать результат.

Пример 🔗

const nums = [1, 2, 3, 4, 5, 6, 7, 8]

// Находим сумму элементов
const sum = nums.reduce(function (currentSum, currentNumber) {
return currentSum + currentNumber
}, 0) // 36

const users = [
{ id: "1", name: "John" },
{ id: "2", name: "Anna" },
{ id: "3", name: "Kate" },
]

// Создаем новый объект с ключом в виде id и значением в виде имени юзера
const usernamesById = users.reduce(function (result, user) {
return {
...result,
[user.id]: user.name,
}
}, {}) // { '1': 'John', '2': 'Anna' , '3': 'Kate' }

Интерактивный пример:

See the Pen reduce by Egor Ogarkov (@Windrushfarer) on CodePen.

Как пишется 🔗

Метод reduce принимает два параметра: функцию-колбэк и начальное значение для аккумулятора.

Сама функция-колбэк может принимать четыре параметра:

  • acc — текущее значение аккумулятора
  • item — элемент массива в текущей итерации
  • index — индекс текущего элемента
  • arr — сам массив, который мы перебираем
const nums = [1, 2, 3, 4, 5, 6, 7, 8]

// Не забываем, что аккумулятор идет первым!
function findAverage(acc, item, index, arr) {
const sum = acc + item

// В конце вычисляем среднеарифметическое делением на кол-во элементов
if (index === arr.length - 1) {
return sum / arr.length
}

return sum
}

const average = nums.reduce(findAverage, 0) // 4.5

Ключом к успешному использованию reduce является внимательно следить за порядком аргументов и не забывать возвращать значение.

Использование reduce похоже на методы forEach, map и filter, в которые так же передается функция-колбэк. Однако в reduce есть дополнительный аргумент — это текущее аккумулируемое значение. При этом можно заметить, что порядок аргументов так же немного изменен.

Функция обязательно должна возвращать значение, т.к в каждой следующей итерации значение в acc будет результатом, который вернулся на предыдущем шаге. Логичный вопрос, который может здесь возникнуть, — какое значение принимает acc во время первой итерации? Им будет являться то самое начальное значение, которые передается вторым аргументом в метод reduce

Как это понять 🔗

Метод reduce крайне полезен, когда мы хотим с помощью манипуляции значениями массива вычислить какое-то новое значение. Такую операцию называют агрегацией. Таким образом у нас появляется мощный инструмент для обработки данных, например это может быть нахождение суммы величин в массиве или группировка в другие типы данных.

Главной особенностью reduce, которую важно запомнить, является наличие аккумулятора. Аккумулятор — это и есть то новое вычисляемое значение. Во время выполнения функции-колбэка нужно обязательно возвращать его значение, т.к оно обязательно попадает в следующую итерацию, где так же будет использоваться для дальнейших вычислений. Таким образом мы можем представить аккумулятор как переменную, значение которой можно поменять в каждой новой итерации. С помощью второго аргумента в reduce эта переменная получает свое начальное значение.

// Задача: вычислить сумму денег на всех счетах
const bankAccounts = [
{ id: "123", amount: 19 },
{ id: "345", amount: 33 },
{ id: "567", amount: 4 },
{ id: "789", amount: 20 },
]

const totalAmount = bankAccounts.reduce(
// Аргумент sum является аккумулятором, в нем храним промежуточное значение
function (sum, currentAccount) {
// Каждую итерацию берем текущее значение и складываем его с количеством
// денег на текущем счету
return sum + currentAccount.amount
},
0 // Начальное значение, которые инициализирует аккумулятор
) // Получим 76

Чтобы понять этот момент можно еще посмотреть на код, который делает то же самое, но уже без reduce:

// Задача: вычислить сумму денег на всех счетах
const bankAccounts = [
{ id: "123", amount: 19 },
{ id: "345", amount: 33 },
{ id: "567", amount: 4 },
{ id: "789", amount: 20 },
]

// Определяем где будем хранить сумму, это в нашем случае является аккумулятором,
// здесь же определяем начальное значение аккумулятора
let totalAmount = 0

for (let i = 0; i < bankAccounts.length; i++) {
const currentAccount = bankAccounts[i]

// В каждой итерации прибавляем к текущей сумме количество денег на счету
totalAmount += currentAccount.amout
}

totalAmount // Будет так же равен 76

И в том и в том другом примере у нас аккумулятор, где хранится текущее значение и кладется новое, есть вычисление нового значение. Только reduce позволяет сделать это в одном месте и более понятном декларативном стиле.

В работе 🔗

🛠 reduce действительно часто применяется для того, чтобы провести математическую операцию для всех элементов массиве и получить в итоге какой-то результат, потому стоит помнить о нем

🛠 Если вы хотите применить подряд несколько операций filter и map, то с помощью reduce их можно объединить в одной функции. Иногда это может быть необходимо в целях производительности, т.к в этом случае будет всего один проход по массиву, вместо нескольких в зависимости от количества вызываемых методов. Но стоит помнить, что такой способ не всегда будет хорошо читаться

// Задача: выбрать четные, вычислить их квадраты и отобрать из них числа больше 50
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

function filterEven(num) {
return num % 2 === 0
}

function square(num) {
return num * num
}

function filterGreaterThanFifty(num) {
return num > 50
}

// Применяем несколько методов
const result = numbers
.filter(filterEven)
.map(square)
.filter(filterGreaterThanFifty) // [64, 100]

// Через один reduce
const result = numbers.reduce(function (res, num) {
if (filterEvens(num)) {
const squared = square(num)

if (filterGreaterThanFifty(squared)) {
res.push(squared)
}
}

return res
}, []) // [64, 100]

🛠 Часто встречается использование reduce для нормирования значений. Например, для превращения массива с данными пользователем в объект, где ключом будет id пользователя, а значением исходный объект. Таким образом можно быстро получать значение объект-пользователя по id, обратившись по ключу к объекту, вместо поиска по массиву:

const users = [
{ id: "123", name: "Vasiliy", age: 18 },
{ id: "345", name: "Anna", age: 22 },
{ id: "567", name: "Igor", age: 20 },
{ id: "789", name: "Irina", age: 24 },
]

const usersById = users.reduce(function (result, user) {
result[user.id] = {
name: user.name,
age: user.age,
}

return result
}, {}) // Получим объект с данными пользователем

usersById["567"] // { name: 'Igor', age: 20 }