метод reduce() в javascript

Давайте посмотрим, что говорит MDN:
Метод reduce() выполняет предоставленную пользователем функцию обратного вызова «reducer» на каждом элементе массива по порядку, передавая возвращаемое значение из вычисления на предыдущем элементе. Конечным результатом работы редуктора над всеми элементами массива является одно значение.

При первом выполнении обратного вызова нет «возвращаемого значения предыдущего вычисления». Если оно задано, вместо него может быть использовано начальное значение. В противном случае в качестве начального значения используется элемент массива с индексом 0, и итерация начинается со следующего элемента (индекс 1 вместо индекса 0).

довольно сложно ☝️.

На самом деле, эта функция довольно сложная по сравнению с map и filter.

Пока поймите следующее: Каждый элемент массива проходит через предоставленную нами функцию, чтобы на выходе получить одно значение.

Лучший способ понять это — на примерах. Итак, давайте начнем:

Пример 1: Учитывая массив чисел, вернуть сумму всех чисел?

const numbersArray = [1, 6, 9, 4];

//we'll understand using normal function:

function sum(accumulator, currentValue){
        return acc+current;
}

const output = numbersArray.reduce(sum);
console.log(output)

Result: 20
Войти в полноэкранный режим Выход из полноэкранного режима

Итак, что, черт возьми, только что произошло. Не волнуйтесь, просто слушайте меня внимательно и при этом обращайтесь к приведенному выше примеру.

Давайте сначала попробуем разобраться в этом 👇 базовом синтаксисе функции reducer:

reduce((accumulator, currentValue) => accumulator + currentValue)
Войти в полноэкранный режим Выход из полноэкранного режима

Функция reducer — это функция, которую мы определяем внутри метода reduce. Это самое важное, что нужно понять. Если вы поймете это, то остальная часть блога будет для вас легкой прогулкой.

аккумулятор: Значение, полученное в результате вызова функции reducer. Самое первое значение аккумулятора — array[0].

currentValue: Текущий элемент массива. Самое первое значение currentValue — array[1]. currentValue — это всегда значение, следующее за тем, которое принял аккумулятор на предыдущем шаге.

Все еще не ясно. Не волнуйтесь, просто пройдитесь по мысленной модели того, что происходит, когда мы вызываем функцию sum в приведенном выше примере, и все станет предельно ясно.

Шаг 1: Поскольку самым первым значением аккумулятора является array[0]. Следовательно, в приведенном выше примере оно будет равно 1.

Самым первым значением currentValue является array[1]. Следовательно, в приведенном выше примере оно будет равно 6.

Шаг 2: В соответствии с инструкциями, данными в функции sum, будет возвращено значение accumulator + currentValue, то есть 1+6 = 7 (мы получили значения и accumulator, и currentValue из приведенного выше шага).

Шаг 3: Возвращенное значение 7 будет сохранено в аккумуляторе для следующей итерации, а значение currentValue теперь будет равно 9.

Шаг 4: В соответствии с инструкциями, данными в функции sum, будет возвращено значение accumulator + currentValue, то есть 7+9 = 16 (мы получили значения accumulator и currentValue из предыдущего шага).

Шаг 5: Возвращенное значение из вышеприведенного шага, равное 16, будет сохранено в аккумуляторе для следующей итерации, а currentValue теперь будет установлено в 4.

шаг 6: В соответствии с инструкциями, данными в функции sum, будет возвращено значение accumulator + currentValue, то есть 16+4 = 20 (мы получили значения как accumulator, так и currentValue из предыдущего шага).

Надеюсь, теперь вы поняли, как работает базовая версия reduce.

Давайте разберем еще один похожий пример:

Пример 2: Учитывая массив чисел, верните умножение этих чисел?

const numbersArray = [1, 6, 9, 4];

//using arrow function:

const output = numbersArray.reduce((acc, current) => acc*current);
console.log(output)

Result: 216
Войти в полноэкранный режим Выйти из полноэкранного режима

Поймите приведенный выше пример на основе ментальной модели, рассмотренной выше.

Теперь, когда вы поняли базовую версию reduce, позвольте мне представить вам немного расширенную версию:

Вот синтаксис:

reduce((accumulator, currentValue) => accumulator + currentValue, initialValue)
Войти в полноэкранный режим Выйти из полноэкранного режима

Итак, что такое initialValue. initialValue — это значение, которым инициализируется наш аккумулятор при первом вызове функции reducer value.

Всякий раз, когда initialValue предоставляется, значение других переменных изменяется. Вот что изменяется при наличии initialValue.

аккумулятор: Самым первым значением аккумулятора является initialValue, если оно задано.

currentValue: Самым первым значением currentValue является array[0], если указано initialValue.

Неясно. Смотрите пример ниже.

Пример 3:

const numbersArray = [1, 6, 9, 4];

//we'll understand using normal function:

function sum(accumulator, currentValue){
        return acc+current;
}

const output = numbersArray.reduce(sum, 2);
console.log(output)

Result: 22
Вход в полноэкранный режим Выход из полноэкранного режима

Здесь☝️ 2 — это начальное значение.

Все еще не ясно.

Давайте еще раз разберемся с помощью ментальной модели:

Шаг 1: Поскольку самое первое значение аккумулятора равно initialValue. Следовательно, в приведенном выше примере оно будет равно 2.

Самым первым значением currentValue на этот раз является array[0]. Следовательно, в приведенном выше примере оно будет равно 1.

Шаг 2: В соответствии с инструкциями, данными в функции sum, будет возвращено значение accumulator + currentValue, то есть 2+1 = 3 (мы получили значения и accumulator, и currentValue из приведенного выше шага).

Шаг 3: Возвращенное значение из вышеприведенного шага, то есть 3, будет сохранено в аккумуляторе для следующей итерации, а значение currentValue теперь будет установлено в 6.

Шаг 4: В соответствии с инструкциями, данными в функции sum, будет возвращено значение accumulator + currentValue, то есть 3+6 = 9 (мы получили значения и accumulator, и currentValue из предыдущего шага).

Шаг 5: Возвращенное значение из вышеприведенного шага, равное 9, будет сохранено в аккумуляторе для следующей итерации, а currentValue теперь будет установлено в 9.

Шаг 6: В соответствии с инструкциями, данными в функции sum, будет возвращено значение accumulator + currentValue, то есть 9+9 = 18 (мы получили значения и accumulator, и currentValue из предыдущего шага).

Шаг 7: Возвращенное значение 18 будет сохранено в аккумуляторе для следующей итерации, а currentValue теперь будет установлено в 4.

Шаг 8: В соответствии с инструкциями, данными в функции sum, будет возвращено значение accumulator + currentValue, то есть 18+4 = 22 (мы получили значения и accumulator, и currentValue из вышеприведенного шага).

Надеюсь, теперь вы поняли, как работает и эта версия.

ЧЕЛОВЕК, НАПИСАНИЕ ЭТОГО БЛОГА ОТНИМАЕТ СЛИШКОМ МНОГО ВРЕМЕНИ. Но я должен закончить, а вы должны понять. Убейте выбор и вы будете свободны (звучит контринтуитивно, но это не так).

Давайте разберем еще несколько примеров, и я буду использовать только стрелочные функции. Если вам нужно, чтобы то же самое решение было написано в терминах обычных функций, сообщите мне об этом в комментариях, и я с удовольствием это сделаю.

Пример 4: Задав массив чисел, найдите сумму всех нечетных чисел.

const numbersArray = [1, 6, 9, 4, 21, 8, 15];
const sumOdd = numbersArray.reduce((acc, current) => current % 2 === 0 ? acc : acc+current, 0)
console.log(sumOdd);

Result: 46
Войти в полноэкранный режим Выйти из полноэкранного режима

В приведенном выше примере на каждой итерации значение аккумулятора будет оставаться неизменным, если встретившееся число четное. Однако предыдущее значение аккумулятора будет добавлено к текущему значению (currentValue), чтобы получить новое значение аккумулятора для следующей итерации, если число четное. Начальное значение (initialValue) равно 0.

Рассмотрим еще один пример для лучшего понимания:

Пример 5: Учитывая массив чисел, отфильтровать наибольшее значение из массива.

const numbersArray = [1, 6, 9, 4, 21, 8, 15];
const max = numbersArray.reduce((acc, current) => acc > current ? acc:current, 0)
console.log(max);

Result: 21
Войти в полноэкранный режим Выйти из полноэкранного режима

Логика в приведенном выше примере: Если accumulator больше, то использовать тот же самый accumulator для следующей итерации, а если currentValue больше, то использовать currentValue в качестве accumulator для следующей итерации.

Приведем еще один пример:
Пример 6: Вернуть объект с суммой нечетных и четных чисел по отдельности?
Я хочу, чтобы возвращалось что-то вроде этого: {evenSum: 142, oddSum: 71}. Помните, я говорил вам, что reduce возвращает только одно значение. Интересный факт: это значение может быть объектом.

const numbersArray = [1, 6, 9, 4, 21, 8, 15];

const sumEvenOdd = numbersArray.reduce((acc, current) => current % 2 === 0 ? {...acc,'even':acc['even'] + current} :  {...acc, 'odd':acc['odd'] + current}, {"even":0, "odd":0})}

console.log(sumEvenOdd)

Result: {even: 18, odd: 46}
Вход в полноэкранный режим Выход из полноэкранного режима

Некоторые из вас могут быть такими же тупыми, как я, и не понимать все сразу. Поэтому вот небольшое объяснение для таких, как я.👇

Примечание: в примере выше я использовал оператор разложения (…acc), если вы не понимаете, что это такое, то лучше сначала прочитать о нем.

Шаг 1: Самое первое значение аккумулятора будет установлено в {«even»:0, «odd»:0}, так как это начальное значение.

Шаг 2: Теперь первый элемент массива будет передан через функцию reducer. Функция reducer проверит, делится ли значение currentValue на 2. Поскольку значение currentValue равно 1 (не делится на 2), функция reducer вернет {…acc, ‘odd’:acc[‘odd’] + current}, что может показаться сложным, но это просто {«even»:0, «odd»:0+1}, то есть {«even»:0, «odd»:1}.

Шаг 3: На этой итерации значение currentValue равно 6, что является четным. Следовательно, редуктор вернет {«even»:0+6, «odd»:1}, что равно {«even»:6, «odd»:1}.

Шаг 4: На этой итерации текущее значение (currentValue) равно 9, что является нечетным. Следовательно, reducer вернет {«even»:6, «odd»:9+1}, что равно {«even»:6, «odd»:10}.

Шаг 5: и так далее, пока не получим {четные: 18, нечетные: 46}.

Теперь еще один пример, и я обещаю, что он будет последним.

Если вы читали мой блог о filter, то там я использовал тот же пример и добился результата, используя и map, и filter. Здесь мы достигнем желаемого результата, используя только reduce.

Помните, я говорил вам, что reduce возвращает только одно значение, и это значение может быть объектом. Вот интересный факт: это значение может быть и массивом.

Пример 7: Дан массив, представленный ниже:

const details = [{'firstName': 'Rajat', 'lastName': 'Gupta', 'age': 28},
{'firstName': 'Barack', 'lastName': 'Obama', 'age': 50},
{'firstName': 'Elon', 'lastName': 'Musk', 'age': 45},
{'firstName': 'Joe', 'lastName': 'Rogan', 'age': 36},
{'firstName': 'Abdul', 'lastName': 'Kalam', 'age': 64}]
Войти в полноэкранный режим Выйти из полноэкранного режима

Можете ли вы отфильтровать FirstName людей, чей возраст превышает 46 лет?

console.log(details.reduce((acc, current) => current.age>46 ?  [...acc, current.firstName] : acc, []))

Result: ['Barack', 'Abdul']
Войти в полноэкранный режим Выйти из полноэкранного режима

Еще раз для тех, кто немного медлителен, как я. Вот небольшое объяснение:

Примечание: в приведенном выше примере я использовал оператор разброса (…acc), если вы не понимаете, что это такое, вам стоит сначала прочитать о нем.

Шаг 1: Самое первое значение аккумулятора будет установлено в пустой массив.

Шаг 2: Теперь первый элемент массива (который является объектом) будет передан через функцию reducer. Функция reducer увидит возраст и если возраст меньше 46 лет, то значение аккумулятора останется прежним для следующей итерации (accumulator = []).

Шаг 3: На этой итерации возраст больше 46 лет, поэтому значение аккумулятора изменится на [‘Barack’].

Шаг 3: Значение аккумулятора останется неизменным в течение следующих 2 итераций.

Шаг 4: На этой итерации возраст больше 46 лет, поэтому значение аккумулятора изменится с [‘Barack’] на [‘Barack’, ‘Abdul’].

Вот и все, друзья.

Если у вас есть какие-то сомнения, спрашивайте меня в разделе комментариев, и я постараюсь ответить как можно скорее.

Каждый день я пишу по одной статье, связанной с веб-разработкой (да, каждый, мать его, день). Следите за мной здесь, если вы изучаете то же самое.

Если вам понравилась статья, следуйте за мной в Twitter: @therajatg

Если вы любите Linkedin, давайте общаться: https://www.linkedin.com/in/therajatg/.

Желаю вам отличного дня 😀!

Оставьте комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *