Pendahuluan

Untuk pendahuluan Fungsional Reduce ini bisa dilihat di Java - Fungsional - Reduce - Part 1

Secara umum bisa kita simpulkan bahwa :

  • Collections adalah seperti kumpulan data yang diletakkan berurutan, contohnya List, Array, Set, Queue, dll.
  • Aggregasi adalah proses untuk melakukan kombinasi dari Collections.
  • Salah satu jenis operasi aggregasi adalah Reduction, yaitu melakukan kombinasi untuk mendapatkan ekstraksi dari data di dalam Collection.
  • Contoh Reduction misalnya adalah fungsi max(), min(), average(), list baru dengan ekstrak data
  • Di dalam java fungsional way, fungsi umum Reduction ini diwakili oleh fungsi Stream.reduce
  • Fungsi Stream.reduce ini menghasilkan satu nilai saja, bukan list baru dengan ekstrak data.
  • Untuk mendapatkan hasil dalam bentuk list ekstraksi , biasanya digunakan juga fungsi Stream.collect

Coba kita lihat lagi code nya :

Fungsi Stream.reduce

Coba kita buat kembali logic Stream.reduce dengan cara fungsional :

 1import java.util.List;
 2
 3public class DemoReduce {
 4
 5	public static void main(String[] args) {
 6
 7      List<Integer> nilaiMahasiswa = List.of(80,65,76,92,55,77,78);
 8
 9      Integer minValue = nilaiMahasiswa
10			             .stream()
11			             .reduce((a, b) -> a > b ? b : a)
12			             .get();
13      System.out.println(minValue);
14	}
15}

Hasilnya :

55

Apa yang kita lakukan ?

  • baris 10 ==> mengubah Collection kita menjadi Stream yang akan lebih mudah digunakan untuk memakai cara fungsional.
  • baris 11 ==> menerapkan fungsi reduce dengan isi code berupa tertier operator –> (a, b) -> a > b ? b : a code ini artinya : melakukan perbandingan antara a dan b, dan mengembalikan mana yang lebih kecil nilainya.
  • baris 12 ==> mengembalikan nilai hasil proses reductionnya tersebut.

Simple toh ?

Iya memang sederhana, tetapi harus diingat, ada 3 komponen utama dari fungsi Stream.reduce ini.

Konsep ini harus diingat, agar tidak terjadi kesalahan dalam menerapkan fungsi reduce ini.

Ketiga komponen tersebut adalah :

  • Identity
  • Accumulator
  • Combiner

Coba kita lihat satu per satu.


1. Identity


Identity adalah nilai awal atau nilai default ketika Collection nya kosong.

Pada contoh code diatas, kita tidak menentukan sendiri nilai awalnya, sehingga secara otomatis nilai identitynya tidak ada, tetapi tetap dilakukan proses kombinasi dari isi Collection.

Misalnya kalau kita memakai identity, lalu mencari nilai sum(), yaitu penjumlahan dari data di Collection, biasanya kita set nilai awalnya adalah 0 sebagai identity.

Seiring dijalankannya fungsi Stream.reduce, maka nilai 0 tersebut akan ditambahkan pertama kali kemudian ditambahkan berurutan dengan data yang ada di dalam collection.

Atau kalau kita mencoba melakukan fungsi reduce dengan kombinasi boolean dengan OR operator, maka biasanya kita set nilainya adalah false.

Atau kalau kita mencoba melakukan fungsi reduce dengan kombinasi boolean dengan AND operator, maka biasanya kita set nilainya adalah true.

Tapi harus hati-hati dengan nilai identity ini.

Bisa saja misalnya kita set nilai awalnya “3” sebagai nilai identity untuk fungsi sum(), misalnya seperti ini :

 1import java.util.List;
 2
 3public class DemoReduce {
 4
 5	public static void main(String[] args) {
 6
 7      List<Integer> nilaiMahasiswa = List.of(80,65,76,10);
 8
 9      Integer minValue = nilaiMahasiswa
10			             .stream()
11			             .reduce(3, (a, b) -> a + b);
12      System.out.println(minValue);
13	}
14}

Maka yang akan terjadi adalah :

iterasi pertama -->  3 (identity) + 80 (data pertama) = 83
iterasi kedua   -->  83 (akumulasi) + 65 (data kedua) = 148
iterasi ketiga  -->  148 (akumulasi) + 76 (data ketiga) = 224
iterasi keempat -->  224 (akumulasi) + 10 (data keempat) = 234

--> Hasil : 234

Akumulasi --> (3) + (80 + 65 + 76 + 10) = 3 + 231 = 234

Perhitungannya benar sekali !!!.

Tapi ketika kita mencoba membuatnya diproses lebih cepat secara paralel dengan cara mengubahnya menjadi paralelStream, maka hasilnya akan jauh berbeda.

 1import java.util.List;
 2
 3public class DemoReduce {
 4
 5	public static void main(String[] args) {
 6
 7      List<Integer> nilaiMahasiswa = List.of(80,65,76,10);
 8
 9      Integer minValue = nilaiMahasiswa
10			             .parallelStream()
11			             .reduce(3, (a, b) -> a + b);
12      System.out.println(minValue);
13	}
14}

Hasilnya :

243
Dengan stream biasa: 234 , sementara dengan parallelStream : 243, berbeda 9 angka.

Bagaimana penjelasannya ?

Ok, ParallelStream berarti fungsi aggregasi yang kita definisikan akan di jalankan di thread yang berbeda, sebanyak core CPU yang komputer kita miliki.

Misalnya komputer kita punya Prosesor dengan 4 core.

Maka saat program diatas dijalankan, maka setiap core CPU akan diassign task untuk memproses bagian data dari Collection yang kita punya.

Lalu akan diimplementasikan fungsi yang sama termasuk dengan nilai identitynya.

Terakhir akan dilakukan akumulasi dari proses diatas.

Coba kita lihat yang terjadi untuk ParallelStream dengan 4 core, adalah :

CPU core pertama -->  3 (identity) + 80 (data pertama) = 83
CPU core kedua   -->  3 (identity) + 65 (data kedua) = 68
CPU core ketiga  -->  3 (identity) + 76 (data ketiga) = 79
CPU core keempat -->  3 (identity) + 10 (data keempat) = 13

--> Hasil : 243

Akumulasi --> (83 + 68 + 79 + 13) = 243

See..

Ternyata kalau kita lakukan pemrosesan secara parallelStream, maka yang terjadi adalah semua core CPU akan menambahkan identity dengan setiap data yang ada di Collection, lalu baru mengakumulasikannya dengan fungsi akumulator.

Akibatnya ekspektasi kita yang seharusnya ingin hasilnya 234, ternyata tidak sesuai kenyataan, malah hasilnya berlebih 9, yaitu 243.

Hal ini tidak diharapkan sebenarnya.

Oleh karena ini nilai identity ini harus mengikuti konsep asal matematika dari identity :

Secara sederhana, jika a adalah identity, maka ketika diterapkan operasi biner antara setiap elemen Collection dengan nilai a, maka nilai elemen collection tadi tidak berubah

wikipedia

Formulasinya adalah :

[identity] [operator] [element_collection] = [element_collection]

Misalkan :

0 + 83 = 83

0 (identity) + (operator) 83 (element) = 83 (element)

Bingung..???

Oke, langsung contohnya saja :

  • identity untuk penjumlahan adalah 0, karena 0 ditambah nilai apapun akan mengembalikan nilai itu kembali.
  • identity untuk perkalian adalah 1, karena 1 dikali nilai apapun akan mengembalikan nilai itu kembali.
  • identity untuk operasi OR adalah false, karena false OR nilai apapun akan mengembalikan nilai itu kembali.
  • identity untuk operasi AND adalah true, karena true AND nilai apapun akan mengembalikan nilai itu kembali.
  • dll.

Dengan pemilihan nilai value yang sesuai dengan konsep Identity diatas, maka kita tidak akan khawatir mendapatkan nilai hasil yang salah walaupun dilakukan pemrosesan secara paralel.

Sehingga identity value yang valid untuk code diatas adalah sbb :

 1import java.util.List;
 2
 3public class DemoReduce {
 4
 5	public static void main(String[] args) {
 6
 7      List<Integer> nilaiMahasiswa = List.of(80,65,76,10);
 8
 9      Integer minValue = nilaiMahasiswa
10			             .parallelStream()
11			             .reduce(0, (a, b) -> a + b);
12      System.out.println(minValue);
13	}
14}

Kita lanjut ke part 3