Я полностью недооценил силу Array.prototype.reduce()
, когда впервые изучал JavaScript. Я не совсем понимал, как это работает, и поэтому списал это на не более чем удобный способ найти сумму элементов массива.
Но теперь, когда я понимаю, как reduce
работает, я вижу его потенциал для самых разных приложений. Чтобы продемонстрировать этот потенциал, я сначала воспользуюсь reduce
для имитации поведения двух наиболее распространенных методов работы с массивами, которые я использую - map
и filter
, а затем покажу, как его можно использовать вместо for
циклов для аккуратной очистки кода.
Уменьшить как карту
Для непосвященных Array.protoype.map()
- это метод, который вы можете использовать в любом массиве для выполнения одного и того же кода для каждого из его элементов для создания нового преобразованного массива. Это выглядит так (>>
указывает возвращаемое значение):
[1, 2, 3].map(num => num + 1)
>> [2, 3, 4]
Метод .map()
принимает функцию обратного вызова в качестве своего первого параметра - здесь я передаю анонимную функцию - и передает каждый элемент в массиве в качестве первого аргумента этой функции обратного вызова.
Тот же результат может быть достигнут с reduce
следующим образом:
[1, 2, 3].reduce((result, num) => [ ...result, num + 1 ], []) >> [2, 3, 4]
Хорошо, так что здесь происходит? Первым параметром функции обратного вызова, переданной в reduce
, является аккумулятор, значение, которое в конечном итоге возвращается reduce
. Каждый раз, когда reduce
запускает эту функцию обратного вызова, возвращается новый аккумулятор и передается в следующую функцию обратного вызова, и так далее, пока он не достигнет конца массива. Че ;;
Второй параметр функции обратного вызова - это фактический элемент в массиве. Итак, то, что я делаю выше, - это копирование аккумулятора в новый массив и добавление текущего элемента массива в новый массив. Этот новый массив становится новым аккумулятором, который затем передается в функцию обратного вызова.
Метод reduce
также отличается от метода map
, поскольку он принимает необязательный второй аргумент, который используется в качестве начального значения аккумулятора. В моем примере я передал пустой массив.
Уменьшить как фильтр
Array.prototype.filter()
имеет структуру, аналогичную map
, но результат сильно отличается. Функция обратного вызова, переданная в filter
, используется для определения, какие значения появляются в новом возвращаемом массиве, а какие нет.
Выглядит это так:
[1, 2, 3].filter(num => num > 1) >> [2, 3]
Когда функция обратного вызова возвращает false
, оцениваемый элемент не включается в возвращаемый массив.
При использовании reduce
это выглядит так:
[1, 2, 3].reduce((result, num) => { return num > 1 ? [ ...result, num ] : result }, []) >> [2, 3]
Я имитировал поведение filter
, вернув троичный оператор. Если num > 1
оценивается как true
, вернуть новый массив с содержимым аккумулятора и текущим значением num
. Если выражение оценивается как false
, вернуть аккумулятор как есть, опуская текущее значение num
из возвращаемого массива.
Сила уменьшения
Теперь, когда у нас есть лучшее представление о том, как использовать reduce
для имитации поведения других методов массива, я хочу продемонстрировать, как использовать reduce
для замены несколько уродливого кода, который я часто пишу (обычно при решении проблем в Code Wars или Звание хакера).
Сначала давайте посмотрим на сценарий. Нам нужно пройти через массив и удалить все дубликаты, возвращая новый массив только с уникальными значениями из исходного массива.
Как программисты, заботящиеся о сложности, мы знаем, что решение методом грубой силы - итерация по всему массиву для каждого значения или, другими словами, вложение итератора в другой итератор - приводит к квадратичной сложности, потому что вы можете рассчитать время, необходимое для этого. возводя в квадрат длину исходного массива.
Вместо того, чтобы искать во всем массиве каждое значение в этом массиве, мы можем создать объект, который будет служить библиотекой, которую мы можем использовать для мгновенного поиска значения и проверки того, произошло ли оно уже. Когда я впервые учился программировать, я написал это решение следующим образом:
const unique = array => { const lib = {} const result = [] for(let i=0; i < array.length; i++) { if(!lib[array[i]]) { result.push(array[i]) lib[array[i]] = true } } return result } unique([1, 2, 3, 3, 4, 5, 5]) >> [1, 2, 3, 4, 5]
Этот код выполняет свою работу, но выглядит не очень хорошо. Давайте посмотрим, как мы можем это исправить с помощью reduce
:
const unique = array => { return array.reduce( (result, num) => { if(!result.lib[num]) { return { lib: { ...result.lib, [num]: true }, arr: [ ...result.arr, num ] } } else { return result } }, { lib: {}, arr: [] } ).arr } unique([1, 2, 3, 3, 4, 5, 5]) >> [1, 2, 3, 4, 5]
Хорошо, возможно, результат не выглядит супер чистым, но я думаю, что этот пример иллюстрирует огромный потенциал reduce
, потому что он показывает, что аккумулятор может быть настолько сложным, насколько вам нужно. Вы можете хранить в аккумуляторе любую необходимую информацию. Просто не забывайте всегда возвращать значение, иначе undefined
заменит ваш аккумулятор и может вызвать неприятную путаницу.
Понравилась эта статья? Если да, то получите больше похожего контента, подписавшись на наш канал YouTube в Decoded!