diff --git a/.yaspeller.json b/.yaspeller.json index 0ad0c2a904..5d5649fd3b 100644 --- a/.yaspeller.json +++ b/.yaspeller.json @@ -264,6 +264,7 @@ "ребайндинг", "ревью(ер|ера|)", "регистронезависимые", + "редьюсер", "резолвинг", "реквест(ом|ов|ы|ами|у|)", "рендер(а|е|ить|много|)", diff --git a/js/doka/array-reduce/index.md b/js/doka/array-reduce/index.md new file mode 100644 index 0000000000..d12ffc8a63 --- /dev/null +++ b/js/doka/array-reduce/index.md @@ -0,0 +1,149 @@ +--- +title: "Array.reduce" +authors: + - windrushfarer +summary: + - редьюсер + - свёртка +--- + +## Кратко + +Метод массива `reduce` позволяет превратить массив в любое другое значение с помощью переданной функции-колбэка и начального значения. Функция-колбэк будет вызвана для каждого элемента массива и всегда должна возвращать результат. + +## Пример + +```js +const nums = [1, 2, 3, 4, 5, 6, 7, 8] + +// Находим сумму элементов +const sum = nums.reduce(function (currentSum, currentNumber) { + return currentSum + currentNumber +}, 0) +// 36 +``` + +```js +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` — сам массив, который мы перебираем + +```js +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`](/js/doka/array-foreach), [`map`](/js/doka/array-map) и [`filter`](/js/doka/array-filter), в которые так же передаётся функция-колбэк. Однако в `reduce` есть дополнительный аргумент — это текущее аккумулируемое значение. При этом можно заметить, что порядок аргументов так же немного изменён. + +Функция обязательно должна возвращать значение, поскольку в каждой следующей итерации значение в `acc` будет результатом, который вернулся на предыдущем шаге. Логичный вопрос, который может здесь возникнуть, — какое значение принимает `acc` во время первой итерации? Им будет являться то самое начальное значение, которые передаётся вторым аргументом в метод `reduce`. + +## Как это понять + +Метод `reduce` крайне полезен, когда мы хотим с помощью манипуляции значениями массива вычислить какое-то новое значение. Такую операцию называют **агрегацией**. Таким образом у нас появляется мощный инструмент для обработки данных, например это может быть нахождение суммы величин в массиве или группировка в другие типы данных. + +Главной особенностью `reduce`, которую важно запомнить, является наличие **аккумулятора**. Аккумулятор — это и есть то новое вычисляемое значение. Во время выполнения функции-колбэка нужно обязательно возвращать его значение, поскольку оно обязательно попадает в следующую итерацию, где так же будет использоваться для дальнейших вычислений. Таким образом мы можем представить аккумулятор как переменную, значение которой можно поменять в каждой новой итерации. С помощью второго аргумента в `reduce` эта переменная получает своё начальное значение. + +Задача: вычислить сумму денег на всех счетах. + +```js +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`. + +Задача: вычислить сумму денег на всех счетах. + +```js +const bankAccounts = [ + { id: "123", amount: 19 }, + { id: "345", amount: 33 }, + { id: "567", amount: 4 }, + { id: "789", amount: 20 }, +] +``` + +Определяем где будем хранить сумму, это в нашем случае является аккумулятором, здесь же определяем начальное значение аккумулятора. + +```js +let totalAmount = 0 + +for (let i = 0; i < bankAccounts.length; i++) { + const currentAccount = bankAccounts[i] + + // В каждой итерации прибавляем + // к текущей сумме количество денег на счету + totalAmount += currentAccount.amout +} +``` + +`totalAmount` здесь будет так же равен 76. + +И в том и в том другом примере у нас аккумулятор, где хранится текущее значение и кладётся новое, есть вычисление нового значение. Только `reduce` позволяет сделать это в одном месте и более понятном декларативном стиле. diff --git a/js/doka/array-reduce/practice/windrushfarer.md b/js/doka/array-reduce/practice/windrushfarer.md new file mode 100644 index 0000000000..cfd512c0a4 --- /dev/null +++ b/js/doka/array-reduce/practice/windrushfarer.md @@ -0,0 +1,78 @@ +--- +tags: + - practice +permalink: false +--- + +🛠 `reduce` действительно часто применяется для того, чтобы провести математическую операцию для всех элементов массиве и получить в итоге какой-то результат, потому стоит помнить о нём. + +🛠 Если вы хотите применить подряд несколько операций `filter` и `map`, то с помощью `reduce` их можно объединить в одной функции. Иногда это может быть необходимо в целях производительности, поскольку в этом случае будет всего один проход по массиву, вместо нескольких в зависимости от количества вызываемых методов. Но стоит помнить, что такой способ не всегда будет хорошо читаться. + +Задача: выбрать чётные, вычислить их квадраты и отобрать из них числа больше 50. + +```js +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 +} +``` + +Применяем несколько методов: + +```js +const result = numbers + .filter(filterEven) + .map(square) + .filter(filterGreaterThanFifty) +// [64, 100] +``` + +Через один `reduce`: + +```js +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`, обратившись по ключу к объекту, вместо поиска по массиву: + +```js +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 } +```