Як працює 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);

Зверніть увагу: початкове значення акумулятора ми встановлюємо вручну — це нуль. На екрані ми побачимо результат кожної ітерації, а також кінцеву суму всіх значень:

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():

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

Айтівець Міноборони США понабирав кредитів і хотів продати рф секретну інформацію

32-річний розробник безпеки інформаційних систем Агентства національної безпеки Джарех Себастьян Далке отримав 22 роки в'язниці…

30.04.2024

Простий та дешевий. Українська Flytech запустила масове виробництво розвідувальних БПЛА ARES

Українська компанія Flytech представила розвідувальний безпілотний літальний апарат ARES. Основні його переваги — недорога ціна…

30.04.2024

Запрошуємо взяти участь у премії TechComms Award. Розкажіть про свій потужний PR-проєкт у сфері IT

MC.today разом з Асоціацією IT Ukraine і сервісом моніторингу та аналітики згадок у ЗМІ та…

30.04.2024

«Йдеться про потенціал мобілізації»: Україна не планує примусово повертати українців із ЄС

Україна не буде примусово повертати чоловіків призовного віку з-за кордону. Про це повідомила у Брюсселі…

30.04.2024

В ЗСУ з’явився жіночий підрозділ БПЛА — і вже можна проходити конкурсний відбір

В Збройних Силах України з'явився жіночий підрозділ з БПЛА. І вже проводиться конкурсний відбір до…

30.04.2024

GitHub на наступному тижні випустить Copilot Workplace — ШІ-помічника для розробників

GitHub анонсував Copilot Workspace, середовище розробки з використанням «агентів на базі Copilot». За задумкою, вони…

30.04.2024