Я полностью недооценил силу 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!