Belajar dengan membaca secara berurutan

reduce — Merangkum Array Menjadi 1 Value

Pelajari reduce yang merangkum array menjadi 1 value: accumulator dan initial value, total, nilai maksimum, grouping, sampai TypeError di array kosong.

Misalnya kamu perlu menghitung total dari rincian sebuah pesanan. map tidak mengubah jumlah element dan filter hanya membuang sebagian, jadi keduanya tetap mengembalikan array. Proses yang merangkum array menjadi 1 value, seperti total atau nilai maksimum, tidak bisa ditulis dengan kedua method ini.

Artikel ini membahas reduce, yang merangkum array menjadi 1 value, beserta initial value yang menjadi titik awalnya.

Merangkum Array Menjadi 1 Value — reduce dan Initial Value

Misalnya kamu ingin menampilkan total harga di keranjang belanja. Kalau ditulis dengan forEach, variable penampung total harus berada di luar loop, sehingga baris mana pun sesudahnya bisa menimpanya. Karena value-nya berubah setiap kali ditambah, variable itu juga harus dideklarasikan dengan let, bukan const.

Method array reduce (method yang meneruskan element ke callback satu per satu dan hanya mengembalikan return value terakhir) memakai callback yang parameter pertamanya adalah accumulator (parameter yang berisi return value dari pemanggilan sebelumnya). Berbeda dengan map dan filter, element-nya ada di posisi kedua. Argument kedua untuk reduce sendiri adalah initial value (value yang dipegang accumulator pada pemanggilan pertama).

const cartItems = [
  { name: "Stand Laptop", price: 3200 },
  { name: "USB Hub", price: 2480 },
  { name: "Kabel HDMI", price: 1180 },
];

// Parameter pertama adalah return value pemanggilan sebelumnya, yang kedua element saat ini
const addPrice = (total, item) => total + item.price;

// Argument kedua, 0, menjadi total pada pemanggilan pertama
const totalPrice = cartItems.reduce(addPrice, 0);
console.log(totalPrice);          // 6860

// Yang dikembalikan adalah 1 value, bukan array
console.log(typeof totalPrice);   // number
total Diganti di Setiap Pemanggilan
Pemanggilan ke-1:total = 0Mengembalikan0 + 3200Pemanggilan ke-2:total = 3200Mengembalikan3200 + 2480Pemanggilan ke-3:total = 5680Mengembalikan5680 + 1180totalPrice= 6860Return valueterakhir disimpan
3200 yang dikembalikan pemanggilan ke-1 diteruskan sebagai total di pemanggilan ke-2. 6860 yang dikembalikan pemanggilan terakhir adalah hasil reduce.

Kamu tidak perlu menyiapkan variable sendiri untuk menyimpan hasil sementara, karena reduce yang bertugas meneruskan value dari satu pemanggilan ke pemanggilan berikutnya. Yang diteruskan juga tidak harus number; bisa juga element atau object.

Dari daftar pemesanan alat tulis kantor, hitung jumlah barang dan biaya seluruh pesanan. orderLines sudah dideklarasikan, dan tiap baris berisi 1 produk.

① Tampilkan total jumlah barang.

② Tampilkan total biaya, dengan biaya tiap baris = harga satuan × jumlah barang.

③ Tampilkan rata-rata biaya per produk, yaitu total di ② dibagi jumlah produk.

(Kalau kodenya berjalan dengan benar, penjelasan akan muncul.)

JavaScript / TypeScript Editor

Jalankan kode untuk melihat output

Menyimpan yang Lebih Besar — Meneruskan Number atau Element

Misalnya kamu ingin tahu video mana yang paling banyak ditonton di antara video yang sudah kamu unggah. Kali ini tidak ada yang dijumlahkan; kamu membandingkan value dan hanya meneruskan yang lebih besar ke pemanggilan berikutnya. Yang perlu kamu putuskan adalah apa yang dipegang accumulator di pemanggilan pertama.

Accumulator bisa berisi number maupun element itu sendiri. Kalau argument kedua diisi 0, yang diteruskan dari satu pemanggilan ke pemanggilan berikutnya adalah number; kalau diisi videos[0], yang diteruskan adalah element itu sendiri. Pakai initial value yang jenisnya sama dengan yang dikembalikan callback.

const videos = [
  { title: "Dasar SQL #1", views: 8200 },
  { title: "Dasar Linux #3", views: 15400 },
  { title: "Pengenalan Git", views: 9700 },
];

// Initial value-nya number, jadi yang dibandingkan dan dikembalikan juga number
const maxViews = videos.reduce((max, video) => Math.max(max, video.views), 0);
console.log(maxViews);         // 15400

// Initial value-nya element, jadi yang dibandingkan dan dikembalikan juga element
const topVideo = videos.reduce(
  (top, video) => (video.views > top.views ? video : top),
  videos[0],
);
console.log(topVideo.title);   // Dasar Linux #3
Apa Pun Initial Value-nya, Itulah yang Diteruskan Sampai Akhir
Initial value 0(number)Bandingkan maxdengan video.viewsKembalikannumber terbesarmaxViews= 15400Initial valuevideos[0]Bandingkantop.views denganvideo.viewsKembalikanelement terbesartopVideo.titlebisa dibaca
Baris atas hanya meneruskan number, sedangkan baris bawah meneruskan element apa adanya. Kalau kamu juga butuh judulnya, pakai element sebagai initial value.

Kedua pemanggilan reduce sama-sama membandingkan views; bedanya hanya apa yang diteruskan. Kalau initial value di baris kedua diganti 0, top.views di pemanggilan pertama bernilai undefined, sehingga perbandingannya selalu false dan yang dikembalikan tetap 0 sampai akhir.

Initial Value 0 Tidak Cocok untuk Bilangan Negatif

Di array yang bisa berisi value negatif, seperti catatan suhu, kalau kamu mulai dari 0, maka 0 tetap dianggap lebih besar walaupun nilai maksimum sebenarnya -3, dan hasilnya jadi value yang tidak ada di array. Kalau yang dibandingkan adalah sesama element dan initial value-nya element pertama array, rentang value-nya tidak lagi berpengaruh.

Dari penjualan dan biaya tiap cabang bulan ini, cari cabang dengan laba tertinggi dan terendah. stores sudah dideklarasikan, dan nilainya dalam satuan 10.000 yen.

① Tampilkan nama cabang dengan laba (penjualan − biaya) tertinggi.

② Tampilkan nama cabang dengan laba terendah.

③ Tampilkan laba cabang di ②.

④ Tampilkan selisih laba antara cabang di ① dan cabang di ②.

JavaScript / TypeScript Editor

Jalankan kode untuk melihat output

Merangkum Jumlah ke dalam Object — Grouping

Misalnya kamu ingin tahu ada berapa tiket support di tiap kategori. Berbeda dengan total, hasilnya bukan 1 number, melainkan 1 value untuk tiap kategori. Kamu bisa menyiapkan object kosong di luar lalu menambah hitungannya dengan forEach, tapi object penghitung itu jadi terpisah dari prosesnya.

{} juga bisa dipakai sebagai initial value. Dalam hal ini accumulator-nya adalah object yang sedang dihitung, dan callback memperbarui sebuah key lalu mengembalikan object itu. Key yang belum ada bernilai undefined saat dibaca, jadi sisipkan ?? 0 supaya hitungannya mulai dari 0.

const tickets = [
  { subject: "Saya ingin mengembalikan barang", category: "Retur" },
  { subject: "Pesanan saya belum sampai", category: "Pengiriman" },
  { subject: "Tolong terbitkan ulang label pengiriman", category: "Pengiriman" },
];

// Tambahkan 1 ke hitungan kategori tiap tiket
const countByCategory = tickets.reduce((counts, ticket) => {
  counts[ticket.category] = (counts[ticket.category] ?? 0) + 1;
  return counts;          // Teruskan counts yang sudah diperbarui ke pemanggilan berikutnya
}, {});                   // {} ini menjadi counts di pemanggilan pertama

console.log(JSON.stringify(countByCategory));  // {"Retur":1,"Pengiriman":2}
console.log(countByCategory["Pengiriman"]);    // 2
Ketiga Pemanggilan Memperbarui Object counts yang Sama
Pemanggilan ke-1:counts = {}Retur jadi 1,lalu dikembalikanPemanggilan ke-2:Retur 1Pengiriman jadi 1,lalu dikembalikanPemanggilan ke-3:Retur 1,Pengiriman 1Pengiriman jadi 2,lalu dikembalikan
Setiap pemanggilan menambah 1 pada sebuah key di counts yang diterimanya, lalu mengembalikan object yang sama. Satu-satunya object yang diubah adalah object yang diberikan sebagai initial value.

{} di initial value adalah object baru yang hanya menampung hasil hitungan, jadi element di tickets tidak diubah sama sekali. Tabel di bawah merangkum pasangan initial value dan return value callback untuk ketiga cara pakai yang sudah dibahas.

Yang ingin dihasilkanInitial valueYang dikembalikan callback
Total0Total sebelumnya ditambah value-nya
Element terbesarElement pertama arrayElement yang lebih besar
Jumlah per kategoriObject kosongObject dengan key yang sudah diperbarui

Hitung penjualan toko online per metode pembayaran. orders sudah dideklarasikan, dan amount adalah nilai 1 pesanan.

① Buat object salesByMethod dengan metode pembayaran sebagai key dan total nilainya sebagai value.

② Ubah salesByMethod menjadi string JSON, lalu tampilkan.

③ Tampilkan porsi kartu kredit dari seluruh penjualan dalam persen (angkanya saja).

④ Tampilkan ada berapa jenis metode pembayaran.

JavaScript / TypeScript Editor

Jalankan kode untuk melihat output

Kalau Tidak Ada Element Sama Sekali — reduce Tanpa Initial Value

Hasil penyaringan bisa saja kosong. Misalnya kamu menghitung total pengiriman hari ini pada hari yang tidak ada pengirimannya sama sekali: tergantung cara menulisnya, reduce bisa mengembalikan 0 atau malah berhenti karena error.

Argument kedua, yaitu initial value, boleh tidak ditulis. Kalau tidak ditulis, element pertama array menjadi accumulator awal, dan callback pertama kali dipanggil dengan element di index 1. Array kosong tidak punya element pertama untuk diambil, jadi yang terjadi adalah TypeError.

const yesterdayAmounts = [3200, 2480, 1180];
const todayAmounts = [];

// Tanpa initial value, element pertama, 3200, menjadi total awal
console.log(yesterdayAmounts.reduce((total, amount) => total + amount));  // 6860

// Di array kosong pun, initial value dikembalikan apa adanya
console.log(todayAmounts.reduce((total, amount) => total + amount, 0));   // 0

// Tanpa initial value, total awal tidak bisa ditentukan
// todayAmounts.reduce((total, amount) => total + amount);
// TypeError: Reduce of empty array with no initial value
Array Kosong dengan dan tanpa Initial Value
Array kosong,initial value 0Tidak ada elementyang diteruskanCallback tidakdipanggilInitial value 0dikembalikanArray kosong,initial valuetidak adaTidak ada elementpertamaTotal awal tidakbisa ditentukanBerhenti karenaTypeError
Array kosong yang sama memberi hasil berbeda tergantung ada atau tidaknya initial value. Element pertama hanya dibutuhkan kalau tidak ada initial value.

Bug seperti ini sulit disadari saat kodenya ditulis: dengan data yang ada, kodenya berjalan normal, dan baru berhenti pada hari pertama ketika tidak ada satu element pun yang cocok. Kalau hasil filter diteruskan ke reduce, berikan initial value untuk berjaga-jaga kalau hasilnya kosong.

Tanpa {}, Element Pertama yang Ikut Diubah

Kalau {} di akhir contoh countByCategory dihapus, object tiket tickets[0] itu sendiri menjadi counts awal. Tidak ada error: object itu malah ditambahi key Pengiriman, dan 1 tiket Retur tidak pernah terhitung. Walaupun array-nya tidak kosong, selalu berikan initial value saat merangkum ke dalam object.

Hitung total refund bernilai besar dari catatan refund bulan ini. refunds sudah dideklarasikan, dan nilainya dalam yen.

① Tampilkan total semua refund tanpa menulis initial value.

② Buat array yang hanya berisi refund 5000 yen ke atas, lalu tampilkan jumlah element-nya.

③ Tampilkan total array di ② dengan memberikan initial value.

④ Tampilkan total array di ② tanpa menulis initial value.

JavaScript / TypeScript Editor

Jalankan kode untuk melihat output
QUIZ

Cek Pemahaman

Jawab setiap pertanyaan satu per satu.

Soal 1Apa yang diterima argument pertama callback yang kamu berikan ke reduce?

Soal 2Kalau reduce yang membandingkan views antar-element lalu mengembalikan yang lebih besar diberi initial value 0, apa yang dikembalikan?

Soal 3Apa yang terjadi kalau kamu memanggil reduce tanpa initial value pada array kosong?