Metode orde tinggi

map, where, dan fold pada Iterable sebagai metode orde tinggi.

Daftar isi

Metode orde tinggi (higher-order method) adalah metode yang menerima fungsi sebagai salah satu argumennya, sehingga perilakunya ditentukan oleh fungsi yang diserahkan pada saat pemanggilan. Koleksi Dart menyediakan metode semacam ini untuk mengubah, menyaring, dan menumpuk data tanpa perulangan yang ditulis manual. Topik ini membahas map, where, fold, reduce, any, every, dan firstWhere pada Iterable, dengan statistik nilai ujian sebagai studi kasusnya.

map: mengubah setiap elemen#

map mengembalikan Iterable baru yang elemennya merupakan hasil penerapan fungsi transformasi pada setiap elemen sumber. Metode ini tidak mengubah koleksi asalnya, karena yang disusun adalah deret hasil yang berdiri sendiri. Fungsi transformasinya ditulis sebagai fungsi panah atau fungsi biasa, dan tipe hasilnya boleh berbeda dari tipe sumbernya, misalnya dari int menjadi double atau String. Nilai kembaliannya bertipe Iterable, dan toList() dipanggil apabila hasilnya ingin disimpan sebagai List yang langsung terhitung.

Contoh berikut mengolah nilai ujian berskala 0 sampai 100 menjadi proporsi terhadap nilai maksimum, lalu menyusun label laporan dari angka yang sama.

dart map-nilai.dart
void main() {
  var nilai = [80, 65, 90, 75];
  var proporsi = nilai.map((n) => n / 100);
  print(proporsi.toList());

  var label = nilai.map((n) => 'Peserta dengan nilai $n');
  print(label.toList());
}
text Keluaran
[0.8, 0.65, 0.9, 0.75]
[Peserta dengan nilai 80, Peserta dengan nilai 65, Peserta dengan nilai 90, Peserta dengan nilai 75]

Pemetaan pertama mengubah int menjadi double, sedangkan pemetaan kedua mengubah angka menjadi String, dan keduanya membuktikan bahwa tipe hasil boleh berbeda dari tipe sumbernya. Koleksi nilai tetap berisi angka aslinya karena map tidak menyentuh sumbernya. Pemanggilan toList() pada kedua hasil menandakan bahwa Iterable keluaran map belum terhitung sebelum diminta, karena map menyimpan deskripsi transformasinya, bukan hasilnya. Pola pemetaan sederhana seperti ini menjadi dasar bagi rantai transformasi yang lebih panjang.

where: menyaring elemen#

where mengembalikan Iterable yang hanya memuat elemen sumber yang membuat fungsi predikatnya mengembalikan true. Predikat (predicate) adalah fungsi yang menerima satu elemen dan mengembalikan bool, misalnya (n) => n >= 75. Sama seperti map, koleksi sumbernya tidak berubah, dan urutan elemen yang lolos mengikuti urutan sumbernya. Metode ini menjadi penyaring dasar untuk statistik, misalnya mengambil nilai yang memenuhi standar kelulusan dari satu kelas.

Contoh berikut menyaring nilai yang mencapai standar 75 ke atas.

dart where-nilai.dart
void main() {
  var nilai = [80, 65, 90, 75];
  var memenuhiStandar = nilai.where((n) => n >= 75);
  print(memenuhiStandar.toList());
  print(memenuhiStandar.length);
}
text Keluaran
[80, 90, 75]
3

Predikat n >= 75 menolak 65, sehingga hasilnya hanya [80, 90, 75] dan length menghitung tiga elemen. Urutan elemen yang lolos tetap 80, 90, 75 karena penyaringan tidak menyusun ulang data sumbernya. Properti length pada hasil where menghitung banyaknya elemen yang lolos, tetapi perhitungan tersebut hanya dilakukan pada saat properti itu diminta. Apabila hasil penyaringan perlu disimpan atau diakses melalui indeks, panggil toList() agar Iterable-nya dimaterialisasi menjadi List. Kedua hasil tersebut menggambarkan data yang sama dari dua sisi, yaitu nilai mana yang lolos standar dan berapa banyak jumlahnya.

fold dan reduce: mengakumulasi nilai#

fold menumpuk seluruh elemen menjadi satu nilai melalui fungsi akumulasi yang diterapkan berurutan. Metode ini menerima nilai awal beserta fungsi yang menggabungkan akumulasi sementara dengan elemen yang sedang diproses, misalnya fold<int>(0, (jumlah, n) => jumlah + n) untuk menjumlahkan seluruh nilai. Tipe hasil fold ditentukan oleh nilai awalnya, sehingga metode ini juga dapat mengubah tipe, misalnya menumpuk daftar angka menjadi satu String. reduce adalah bentuk fold tanpa nilai awal, yaitu elemen pertama menjadi akumulasi awalnya, sehingga tipe hasilnya tetap sama dengan tipe elemennya.

Contoh berikut menghitung total, rata-rata, dan nilai terbesar dari satu kelas ujian, lalu menunjukkan perilaku reduce terhadap daftar kosong.

dart fold-nilai.dart
void main() {
  var nilai = [80, 65, 90, 75];
  var total = nilai.fold<int>(0, (jumlah, n) => jumlah + n);
  print(total);
  print(total / nilai.length);

  var terbesar = nilai.reduce((a, b) => a > b ? a : b);
  print(terbesar);

  var kosong = <int>[];
  try {
    kosong.reduce((a, b) => a + b);
  } catch (e) {
    print(e.runtimeType);
    print(e);
  }
}
text Keluaran
310
77.5
90
StateError
Bad state: No element

Total 310 diperoleh dari penjumlahan berurutan 80, 65, 90, dan 75, sedangkan pembagian terhadap length menghasilkan rata-rata 77.5. Nilai terbesar 90 diperoleh dari reduce yang membandingkan dua nilai pada setiap langkahnya dan menyimpan yang lebih besar. Pemanggilan reduce pada daftar kosong melempar StateError dengan pesan Bad state: No element, karena tidak ada elemen pertama yang dapat menjadi akumulasi awalnya.

any, every, dan firstWhere#

Tiga metode berikutnya menguji elemen melalui predikat tanpa mengubah isinya. any mengembalikan true apabila sekurang-kurangnya satu elemen memenuhi predikat, sedangkan every mengembalikan true hanya apabila seluruh elemen memenuhinya. firstWhere mengembalikan elemen pertama yang memenuhi predikat, dan parameter orElse menyediakan nilai bawaan apabila tidak ada elemen yang lolos. Ketiganya menghentikan pemeriksaan lebih awal begitu jawabannya pasti, sehingga tidak seluruh elemen diuji.

Contoh berikut memeriksa kelas ujian yang memiliki nilai di bawah 70, memastikan seluruh peserta mencapai 60, lalu mengambil nilai yang perlu remedial.

dart predikat-nilai.dart
void main() {
  var nilai = [80, 65, 90, 75];
  print(nilai.any((n) => n < 70));
  print(nilai.every((n) => n >= 60));

  var perluRemidi = nilai.firstWhere((n) => n < 70);
  print(perluRemidi);

  var nilaiSempurna = nilai.firstWhere(
    (n) => n == 100,
    orElse: () => -1,
  );
  print(nilaiSempurna);
}
text Keluaran
true
true
65
-1

Pemanggilan any mengembalikan true karena 65 berada di bawah 70, sedangkan every mengembalikan true karena tidak ada nilai di bawah 60. Elemen pertama yang lolos predikat n < 70 adalah 65, sehingga itulah nilai yang memerlukan remedial. Predikat n == 100 tidak dipenuhi oleh satu elemen pun, sehingga orElse menghasilkan -1 sebagai penanda bahwa tidak ada nilai sempurna. Nilai bawaan seperti -1 sebaiknya dipilih agar tidak mungkin tertukar dengan data yang sah.

Ringkasan#

  • map mengubah setiap elemen melalui fungsi transformasi dan mengembalikan Iterable yang belum terhitung sebelum diminta.
  • where menyaring elemen dengan predikat bool tanpa mengubah urutan sumbernya.
  • fold menumpuk elemen menjadi satu nilai dengan nilai awal, sedangkan reduce menggunakan elemen pertama dan melempar StateError pada koleksi kosong.
  • any, every, dan firstWhere menguji elemen dengan predikat dan berhenti lebih awal begitu hasilnya pasti.

Referensi#