Множество применений Reduce

Метод массива reduce часто представляется вместе с map и filter, но это настолько мощный метод, что я посчитал, что он заслуживает отдельного поста. Традиционный пример, используемый для введения reduce, — это следующая функция, которая вычисляет сумму всех элементов массива:

const array = [1, 2, 3, 4, 5];
const sum = array.reduce((a, b) => a + b);
Войти в полноэкранный режим Выйти из полноэкранного режима

Из этого примера вы можете начать развивать интуицию, что этот метод уменьшает элементы в массиве до одного значения, и он, конечно, может и делает это во многих случаях. Однако, поскольку значение может быть практически любым в JavaScript, результат уменьшения не обязательно будет одним примитивным значением или даже меньше исходного массива (если вы сможете придумать какое-то понятие размера для их сравнения).

Вот абстракция, которую обеспечивает reduce:

const array = [1, 2, 3, 4, 5];
const INITIAL_VALUE = 0;

const reduceFunction = (accumulator, element) => accumulator + element;

// Without reduce
let accumulator = INITIAL_VALUE;
for (let i = 0; i < array.length; i++) {
  accumulator = reduceFunction(accumulator, array[i])
}

// With reduce
const accumulator = arrray.reduce(reduceFunction, INITIAL_VALUE);
Войти в полноэкранный режим Выход из полноэкранного режима

Функция reduceFunction, также известная как reducer, принимает два значения и возвращает значение того же типа, что и первый аргумент. Это возвращаемое значение используется в качестве первого аргумента следующей итерации. Если начальное значение не указано, то в качестве начального значения будет использоваться первый элемент массива. Реализация метода reduce на прототипе массива делает его экземпляром Foldable, и Haskell называет эту функцию foldl (для fold слева). Давайте посмотрим на некоторые вещи, которые может делать reduce!

Карта

Вы можете использовать reduce для замены map. Преимущества этого подхода не очевидны сразу, но он будет полезен, когда мы будем рассматривать преобразователи в будущем.

const array = [1, 2, 3, 4, 5];
const mapFunc = (number) => number * 2;

// With map
const newarray = array.map(mapFunc);

// With reduce
const mapReducer = (func) => (accumulator, element) =>
  [...accumulator, func(element)];
const newarray = array.reduce(mapReducer(mapFunc), []);
Вход в полноэкранный режим Выход из полноэкранного режима

Фильтр

Вы можете использовать reduce для замены filter, и это также будет полезно, когда мы будем говорить о преобразователях.

const array = [1, 2, 3, 4, 5];
const predicate = (number) => number % 2 === 0;

// With filter
const newarray = array.filter(predicate);

// With reduce
const filterReducer = (predicate) => (accumulator, element) =>
  predicate(element) ? [...accumulator, element] : accumulator;
const newarray = array.reduce(filterReducer(predicate), []);
Войти в полноэкранный режим Выход из полноэкранного режима

Различные агрегаты

Практически все, что можно создать из массива, может быть создано с помощью reduce. Мне особенно нравится эта реализация создания верхней треугольной матрицы массива. Функция reduce принимает необязательный третий аргумент, который является индексом элемента. (Она также принимает четвертый необязательный аргумент, который является самим массивом).

// Using nested for loops
const upperTriangle = (arr) => {
  let triangle = [];
  for (let first = 0; first < arr.length; first++) {
    for (let second = first + 1; second < arr.length; second++) {
      triangle.push([arr[first], arr[second]]);
    }
  }
  return triangle;
};

// Using reduce and map
const upperTriangle = (arr) =>
  arr.reduce((triangle, first, i) => {
    const rest = arr.slice(i + 1);
    const pairs = rest.map(second => [first, second]);
    return [triangle, pairs].flat();
  }, []);
Вход в полноэкранный режим Выход из полноэкранного режима

Состав функции

Вы правильно прочитали. Вы можете реализовать композицию функций с помощью reduce!

const toWords = (string) => string.split(" ");
const count = (array) => array.length;
const wpm = (wordCount) => wordCount * 80;

const speed = (string) =>
  [toWords, count, wpm]
  .reduce((composed, fn) => fn(composed), string);
Войти в полноэкранный режим Выход из полноэкранного режима

Рекурсивные функции

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

const factorial = (number) =>
  number === 0 ? 1 : number * factorial(number - 1);

const factorial = (number) =>
  Array(number)
    .fill(number)
    .reduce((acc, elem, i) => acc * (elem - i));
Вход в полноэкранный режим Выход из полноэкранного режима

Сумма и друзья

Давайте вернемся к функции sum, с которой мы начали. Оказывается, существует множество примеров, которые следуют аналогичному шаблону:

const numbers = [1, 2, 3, 4, 5];
const sum = numbers.reduce((a, b) => a + b, 0);
const product = numbers.reduce((a, b) => a * b, 1);
const min = numbers.reduce((a, b) => (a < b ? a : b), Infinity);
const max = numbers.reduce((a, b) => (a > b ? a : b), -Infinity);

const booleans = [true, false, false, true];
const any = booleans.reduce((a, b) => a || b, false);
const all = booleans.reduce((a, b) => a && b, true);
Вход в полноэкранный режим Выход из полноэкранного режима

Во всех этих случаях начальное значение можно не указывать, но я включил их для наглядности. Все эти редукторы берут два элемента одного типа и возвращают другой элемент того же типа. Это свойство в сочетании с соответствующими начальными значениями (известными как тождества) образует определение моноида. В следующем посте мы более подробно рассмотрим моноиды и различные места, где они встречаются в программировании.

Надеюсь, этот пост помог вам лучше понять применение reduce. В сочетании с map и filter я уже редко пишу цикл for или while. Императивные циклы более полезны, если вам нужно сделать что-то определенное количество раз, но, как мы скоро увидим, лучше работать с выражениями значений, чем с простыми утверждениями.

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

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