Lab

JavaScript: Использование Map, Filter, и Reduce вместе

Это заключительная статья мини курса по обработки данных в JavaScript. Где мы научимся создавать цепочку вызовов всех изученых ранее методов, для получения более комплексных и интересных манипуляций с данными.

Содержимое мини-курса:

Рассмотрим следующие данные:

data = [
  {
    name: 'Butters',
    age: 3,
    type: 'dog'
  },
  {
    name: 'Lizzy',
    age: 6,
    type: 'dog'
  },
  {
    name: 'Red',
    age: 1,
    type: 'cat'
  },
  {
    name: 'Joey',
    age: 3,
    type: 'dog'
  },
];

Давайте представим, что у нас есть коллекция объектов. Каждый объект представляет собой домашнее животное. У домашних животных есть имя, возраст и тип.

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

  1. Выбираем из коллекции только собак
  2. Переводим возраст на человеческий манер (умножаем на семь)
  3. Суммируем результаты

По традиции показываю как бы мы это делали ранее, до прохождения нашего миникурса используя цикл for:

function getAges(data) {
  let sum = 0;
  for (var i = 0; i < data.length; i++){
    if (data[i].type === 'dog'){
      let tempAge = data[i].age;
      sum += (tempAge * 7);
    }
  }
  return sum;
}
// getAges(data) = 84

Сначала мы создали переменную sum и задали ей значение равное 0. Затем перебираем наш массив, по одному объекту за раз. Если на данной итерации питомец — собака, берем возраст этой собаки, умножаем ее на семь и добавляем полученное значение к нашей сумме. Повторяем эту операцию для каждой собаки в массиве. Когда наша цикл заканчивается, возвращаем сумму.

В результате отработки этого кода мы молучаем sum = 84. Этот код работает но как вы наверное уже догадались, эту задачу можно решить более элегантным способом.

Давайте теперь решим эту-же задачу используя функции map(), reduce() и filter(). Для налача давайте отфильтруем наши данные по типу животного, где нас интересуют собаки:

let ages = data.filter((animal) => {
  return animal.type === 'dog';
})

После того как мы получили коллекцию с типом интересующих нас данных, будем искать возраст собак и умножать каждый на 7. Сделаем это с помощью функции map(). Наша функция map() просто вернет возраст животных, умноженный на 7.

.map((animal) => {
  return animal.age *= 7
})

Ну и наконец нам нужно сложить возраст всех собак, в этом нам поможет метод Reduce()

.reduce((sum, animal) => {
  return sum + animal.age;
});

Таким образом, когда все три этапа завершены, мы просто объединяем предыдущие действия. Вот как будет выглядеть наш код:

let ages = data
  .filter((animal) => {
    return animal.type === 'dog';
}).map((animal) => {
    return animal.age * 7
}).reduce((sum, animal) => {
    return sum + animal.age;
});
// ages = 84

Как и ожидалось, результатом выполнения будет 84 в переменной ages. Но наш код всё ещё трудно читаемый. Что бы исправить это, давайте создатим три чистые функции и будем вызывать их по цепочке.

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

Дл начала давайте создадим функцию, которая проверяет, является ли переданный елемент собакой, и возвращает на выходе либо true либо false

let isDog = (animal) => {
  return animal.type === 'dog';
}

Затем создадим функцию которая умножает на 7 свойство возраст из переданного элемента и возвражает возраст измеряемый в собачьих годах

let dogYears = (animal) => {
  return animal.age * 7;
}

И теперь нам нужна функция которая будет складывать оба результата из предыдущих функций и возвращать нам значение

let sum = (sum, animal) => {
  return sum + animal;
}

Теперь когда у нас есть все три функции мы можем выполнить их как цепочку

let ages = data
  .filter(isDog)
  .map(dogYears)
  .reduce(sum);
// ages = 84

Такой подход в организации кода гораздо проще для чтения и понимания. На выходе мы получаем чистые функции и возможность переиспользования логических блоков кода по мере необходимости.