Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Переносит статью «Array.reduce» #819

Merged
merged 5 commits into from
Jun 12, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .yaspeller.json
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,7 @@
"ребайндинг",
"ревью(ер|ера|)",
"регистронезависимые",
"редьюсер",
"резолвинг",
"реквест(ом|ов|ы|ами|у|)",
"рендер(а|е|ить|много|)",
Expand Down
149 changes: 149 additions & 0 deletions js/doka/array-reduce/index.md
Original file line number Diff line number Diff line change
@@ -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' }
```

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

<p class="codepen" data-height="703" data-theme-id="light" data-default-tab="result" data-user="Windrushfarer" data-slug-hash="NWNmRje" style="height: 703px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;" data-pen-title="reduce">
<span>See the Pen <a href="https://codepen.io/Windrushfarer/pen/NWNmRje">
reduce</a> by Egor Ogarkov (<a href="https://codepen.io/Windrushfarer">@Windrushfarer</a>)
on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>

## Как пишется

Метод `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` позволяет сделать это в одном месте и более понятном декларативном стиле.
78 changes: 78 additions & 0 deletions js/doka/array-reduce/practice/windrushfarer.md
Original file line number Diff line number Diff line change
@@ -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 }
```