Как работает reduce() в JavaScript

Сергій Бондаренко

Что такое метод reduce()

Обычно говорят, что reduce() в JS используется для свертки массивов. Это определение слишком размыто, поэтому мы попробуем его пояснить.

Метод reduce() приводит массив к единому значению, то есть редуцируя (сокращая, сводя) его. Он относится к группе методов, используемых для переборки значений массива, дополняя такие методы как forEach, filter, map и every/some.

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

Синтаксис reduce() в JavaScript

Array.reduce() в качестве параметра задействует метод функции обратного вызова, применяемый для перебора каждого элемента в массиве, а также аргументы аккумулятора (total) и исходное значение initialValue.

До начала перебора, вычисляемое значение (если мы не определим его самостоятельно) равно нулевому элементу массива:

array.reduce(functioncallback(total, currentValue, currentIndex, array), initialValue)

Следующий параметр functioncallback currentValue, каждый следующий элемент массива. Далее — два необязательных параметра: index — номер элемента в массиве, который обрабатывается, а также array — массив, с которым работает reduce().

Последний параметр используется крайне редко (в тех случаях, когда массив мутируется) — так чаще всего у нас есть доступ к массиву по какой-то переменной. 

Примеры reduce() в JavaScript

1Суммирование чисел

Рассмотрим работу метода reduce() на примере вычисления суммы значений элементов массива. Предположим, мы работаем с массивом товаров и ставим цель — подсчитать стоимость всех наименований:

const products_items = [

    {
        title: "Processor Intel Core i7",
        price: 200
    },
    {
        title: "laptop MSI CX70",
        price: 700
    },
    {
        title: "TV-box",
        price: 150
    },
    {
        title: "Desktop",
        price: 2500
    }    
];

const totalPrice = products_items.reduce((acc, products_items, i, arr) => {
    console.log(acc)
    return acc + products_items.price;
}, 0);

console.log('Суммарная стоимость всех позиций -', totalPrice);

Обратите внимание: первоначальное значение аккумулятора мы устанавливаем вручную — это ноль. На экране мы увидим результат каждой итерации, а также конечную сумму всех значений price:

0

200

900

1050

Суммарная стоимость всех позиций - 3550

2Пример вычисления среднего значения в массиве

Функция reduce() позволит просто перебрать элементы массива, сведя его к среднему значению всех элементов:

// Заданный набор чисел, который необходимо обработать
const massiv_chisel = [1, 2, 3, 4, 37, 47, 23, 17];
const srednee = (massiv_chisel) =>{
    const total = massiv_chisel.reduce((total, number) => {
        return total + number; // Суммирование всех элементов массива  
    }, 0); // Исходное значение не забываем определить равным 0
//Вычисление среднего значения  как отношение суммы элементов к длине массива

    return total / massiv_chisel.length; 
}
console.log(srednee(massiv_chisel)); //отображение результата на экран

Результат вычислений:

16,75

3Отбор элементов массива по свойствам

Инструмент reduce() в некоторых случаях значительно упрощает код и делает его компактнее.

Например, представьте себе, что у вас есть массив товаров разного свойства, например, флешки объемом 128 Гб, 512 Гб и 256 Гб. Вам нужно создать новый массив, который будет содержать имена тех производителей, чьи флешки есть на 512 Гб.

Для решения этой задачи мы применим фильтр, который будет отсеивать из данного массива значения, удовлетворяющие условию емкость USB носителя равна 512, а затем применим метод Array.map(), который создаст нам новый массив, содержащий только список имен производителей. 

const USB_drives = [
    {
      vendor: 'Samsung',
      Flash_Drive_Capacity: 128
    },
    {
      vendor: 'Goodram',
      Flash_Drive_Capacity: 512
    },
    {
      vendor: 'Apacer',
      Flash_Drive_Capacity: 32
    },
    {
      vendor: 'SanDisk',
      Flash_Drive_Capacity: 512
    },
    {
      vendor: 'Verbatim',
      Flash_Drive_Capacity: 512
    },
    {
      vendor: 'Transcend',
      Flash_Drive_Capacity: 256
      },
    {
      vendor: 'Kingston',
      Flash_Drive_Capacity: 256
    }];

    const vendors = USB_drives.filter(function (assembly) {
        return assembly.Flash_Drive_Capacity === 512;
      }).map(function (assembly) {
        return assembly.vendor;
      });
      console.log(vendors);

В консоли мы увидим результат обработки массива:

[ 'Goodram', 'SanDisk', 'Verbatim' ]

С использованием reduce() эта задача решается проще. Мы можем отказаться от комбинации методов Array.map() и Array.filter().

Применение метода Array.reduce() дает возможность увеличить производительность кода, поскольку в этом случае происходит перебор элементом массива в один проход.

   const vendors = USB_drives.reduce(function (newArr, assembly) {
    if (assembly.Flash_Drive_Capacity === 512) {
      newArr.push(assembly.vendor);
    }
    return newArr;
  }, []);

      console.log(vendors);

4Сведение данных из нескольких массивов

Рассмотрим другой пример — когда требуется объединить два массива в один. Допустим у нас есть перечень все тех же USB-накопителей с указанием производителя и емкости.

const USB_drives = [
    {
      vendor: 'Samsung Group',
      Flash_Drive_Capacity: 128
    },
    {
      vendor: 'Goodram',
      Flash_Drive_Capacity: 512
    },
    {
      vendor: 'Apacer',
      Flash_Drive_Capacity: 32
    },
    {
      vendor: 'Western Digital',
      Flash_Drive_Capacity: 512
    },
    {
      vendor: 'Verbatim Corp',
      Flash_Drive_Capacity: 512
    },
    {
      vendor: 'Transcend',
      Flash_Drive_Capacity: 256
      },
    {
      vendor: 'Kingston',
      Flash_Drive_Capacity: 256
    }];

Второй массив данных — это данные о скорости чтения устройств.  

 const speed = {
        SamsungGroup: 150,
        WesternDigital: 75,
        Apacer: 100,
        VerbatimCorp: 270

      };

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

    const specification_with_speed = USB_drives.reduce(function (arr, assembly) {
        // Получаем значение для объекта speed, удалив пробелы из имени вендора
    const key = assembly.vendor.replace(' ', '');
        // Если для позиции имеется значение - добавляем его
        // В противном случае ставим прочерк.
    if (speed[key]) {
        assembly.speed = speed[key];
        } else {
            assembly.speed = '-';
        }
        // Добавляем объект assembly 
        arr.push(assembly);
        // Возвращаем массив
        return arr;
      }, []);
// Выводим на экран результат
      console.log(specification_with_speed);

В итоге получаем:

 { vendor: 'Samsung Group', Flash_Drive_Capacity: 128, speed: 150 },

  { vendor: 'Goodram', Flash_Drive_Capacity: 512, speed: '-' },

  { vendor: 'Apacer', Flash_Drive_Capacity: 32, speed: 100 },

  { vendor: 'Western Digital', Flash_Drive_Capacity: 512, speed: 75 },

  { vendor: 'Verbatim Corp', Flash_Drive_Capacity: 512, speed: 270 },

  { vendor: 'Transcend', Flash_Drive_Capacity: 256, speed: '-' },

  { vendor: 'Kingston', Flash_Drive_Capacity: 256, speed: '-' }

5Сведение данных из двух массивов в объект

Метод Array.reduce() позволяет решить и другую задачу — когда данные из двух массивов необходимо преобразовать в объект.

Например, название фирмы-производителя — это будет ключ, а объем накопителя и его скорость — это свойства.

const USB_drives = [
{
vendor: 'Transcend',
USB_capacity: 512
},
{
vendor: 'GOODRAM',
USB_capacity: 128
},
{
vendor: 'Samsung',
USB_capacity: 256
},
{
vendor: 'Apacer',
USB_capacity: 64
},
{
vendor: 'Western Digital',
USB_capacity: 128
}
];

const speed = {
Samsung: 100,
Transcend: 50,
Apacer: 120,
WesternDigital: 121
};
const USB_drivesAsAnObject = USB_drives.reduce(function (obj, usb) {
// Удаляем пробел в названии вендора
const key = usb.vendor.replace(' ', '');
// Если носитель содержит сведения о скорости, добавим их
// В противном случае присвоим ноль
if (speed[key]) {
usb.speed = speed[key];
} else {
usb.speed = 0;
}
// Удаляем свойство vendor 
delete usb.vendor;
// Добавляем usb данные в новый объект
obj[key] = usb;
// Возвращаем массив 
return obj;
}, {});
console.log(USB_drivesAsAnObject);

Отображение результата в консоли:

{
  Transcend: { USB_capacity: 512, speed: 50 },
  GOODRAM: { USB_capacity: 128, speed: 0 },
  Samsung: { USB_capacity: 256, speed: 100 },
  Apacer: { USB_capacity: 64, speed: 120 },
  WesternDigital: { USB_capacity: 128, speed: 121 }
}

6Объединение свойств массивов по критериям

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

Опять же воспользуемся методом reduce():

const documents = [{
    text: "Варкалось, хливкие шорьки пырялись по наве",
    author: "Доджсон",
    date: "1865-02-17 05:12:11"
}, {
    text: "И хрюкотали зелюки",
    author: "Льюис",
    date: "1853-03-07 02:22:14"
}, {
    text: "Как мюмзики в мове",
    author: "Кэрролл",
    date: "1845-12-11 15:02:44"    
}];
const summaryDocuments = (documents) => {
    return documents.reduce((generalDocument, document) =>{
        generalDocument.text = generalDocument.text + " " + document.text;
        generalDocument.authors.push(document.author); 
        if (!generalDocument.date || 
            new Date(generalDocument.date).valueOf() <= new Date(document.date)){
            generalDocument.date = document.date;
        }
        return generalDocument;
    }, {
        text: "",
        authors: [],
        date: null
    });
}
console.log(summaryDocuments(documents));

Результат склейки документов отображается на экране:

{
  text: ' Варкалось, хливкие шорьки пырялись по наве И хрюкотали зелюки Как мюмзики в мове',
  authors: [ 'Доджсон', 'Льюис', 'Кэрролл' ],
  date: '1865-02-17 05:12:11'
}

7Группировка похожих элементов в массив

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

const _ = require('lodash'); //подключаем  все методы библиотеки
chisla = [7.1, 4.1, 1.3, 7.987, 2.5, 4.76]; //исходный набор элементов
console.log(chisla); //вывод в консоль исходного набора чисел
console.log(_.groupBy(chisla, Math.floor)); //вывод с группировкой по целочисленному значению

Результат на экране:

[ 7.1, 4.1, 1.3, 7.987, 2.5, 4.76 ]
{ '1': [ 1.3 ], '2': [ 2.5 ], '4': [ 4.1, 4.76 ], '7': [ 7.1, 7.987 ] }

Массив слов также можно отсортировать по длине строковых данных:

const slova = ['winter', 'cat', 'bot', 'snow', 'father']; //исходный набор элементов
console.log(slova);
console.log(_.groupBy(slova, 'length'));

Отображение в консоли:

[ 'winter', 'cat', 'bot', 'snow', 'father' ]
{ '3': [ 'cat', 'bot' ], '4': [ 'snow' ], '6': [ 'winter', 'father' ] }

8Альтернативная реализация функции groupBy() с помощью Array.reduce()

Данную функциональность можно реализовать и без библиотеки lodash — с помощью Array.reduce(). Напишем функцию, аргументами которой будут массив и критерии сортировки. Внутри этой функции мы будем предавать пустой объект и возвращать результат:

const arr = [7.1, 4.1, 1.3, 7.987, 2.5, 4.76];
const slova = ['winter', 'cat', 'bot', 'snow', 'father'];
const groupBy = function (arr, criteria) {
    return arr.reduce(function (obj, item) {
      //Выполнение проверки - является ли критерий группировки функцией элемента или свойством элемента
      const key = typeof criteria === 'function' ? criteria(item) : item[criteria];
      // Если свойство не создано, создаем его.
      if (!obj.hasOwnProperty(key)) {
        obj[key] = [];
      }
  // Помещаем значение в объект
  obj[key].push(item);
      // Возврат объекта для новой итерации
  return obj;
    }, {});};
    console.log(groupBy(arr, Math.floor));
    console.log(groupBy(slova, 'length'));

Заключение

Надеемся, что описанные выше примеры помогли вам более ясно ощутить преимущества reduce() при решении разных задач в JavaScript.

Для закрепления материала рекомендуем посмотреть видео с разбором разных задач, где требуется применить функцию  reduce() :

Останні статті

Обучение Power BI – какие онлайн курсы аналитики выбрать

Сегодня мы поговорим о том, как выбрать лучшие курсы Power BI в Украине, особенно для…

13.01.2024

Work.ua назвал самые конкурентные вакансии в IТ за 2023 год

В 2023 году во всех крупнейших регионах конкуренция за вакансию выросла на 5–12%. Не исключением…

08.12.2023

Украинская IT-рекрутерка создала бесплатный трекер поиска работы

Unicorn Hunter/Talent Manager Лина Калиш создала бесплатный трекер поиска работы в Notion, систематизирующий все этапы…

07.12.2023

Mate academy отправит работников в 10-дневный оплачиваемый отпуск

Edtech-стартап Mate academy принял решение отправить своих работников в десятидневный отпуск – с 25 декабря…

07.12.2023

Переписки, фото, история браузера: киевский программист зарабатывал на шпионаже

Служба безопасности Украины задержала в Киеве 46-летнего программиста, который за деньги устанавливал шпионские программы и…

07.12.2023

Как вырасти до сеньйора? Девелопер создал популярную подборку на Github

IT-специалист Джордан Катлер создал и выложил на Github подборку разнообразных ресурсов, которые помогут достичь уровня…

07.12.2023