Set
Koleksi unik tanpa duplikat: kapan memakai Set dan operasinya.
Koleksi tanpa duplikat
List boleh berisi nilai kembar: ['apel', 'apel'] valid. Tapi kadang duplikat itu masalah: daftar email undangan, tag artikel, ID pengguna. Set adalah koleksi yang otomatis menolak nilai duplikat.
Analogi: list itu seperti daftar hadir yang bisa ditandatangani dua kali oleh orang yang sama. Set itu seperti cap stempel "sudah hadir", dicap dua kali pun tetap satu.
Kenapa Set ada?
Tiga kegunaan utama: menghilangkan duplikat, mengecek keanggotaan super cepat, dan operasi himpunan (gabungan, irisan). Untuk daftar yang urutannya penting dan boleh kembar, tetap pakai list. Untuk "kumpulan unik", pakai set.
Membuat dan memakai Set
void main() {
// Set literal memakai kurung kurawal
var buah = {'apel', 'mangga', 'pisang'};
print(buah); // {apel, mangga, pisang}
buah.add('apel'); // duplikat: DITOLAK diam-diam
print(buah); // {apel, mangga, pisang}, tetap 3!
buah.add('durian');
print(buah); // {apel, mangga, pisang, durian}
print(buah.contains('mangga')); // true, pengecekan sangat cepat
print(buah.length); // 4
}Perhatikan: menambah 'apel' kedua kalinya tidak error, tapi juga tidak mengubah apa-apa. Set diam-diam menjaga keunikannya.
Contoh kedua, membersihkan duplikat dari list:
void main() {
var email = [
'[email protected]',
'[email protected]',
'[email protected]',
'[email protected]',
'[email protected]',
];
// Trik klasik: List -> Set -> List
var unik = email.toSet().toList();
print(unik); // [[email protected], [email protected], [email protected]]
}.toSet() membuang duplikat dalam satu langkah. Ini pola yang akan sering kamu pakai saat membersihkan data.
Hati-hati: Set vs Map literal
Keduanya memakai {}, dan ini jebakan bagi pemula:
void main() {
var a = {'apel', 'mangga'}; // SET: isinya nilai saja
var b = {'nama': 'Budi'}; // MAP: isinya pasangan key: value
print(a.runtimeType); // _Set<String>
print(b.runtimeType); // _Map<String, String>
// Set kosong butuh anotasi eksplisit!
var kosong = <String>{}; // Set<String> kosong
// var ambigu = {}; // ini Map, bukan Set!
print(kosong);
}Dart menebak dari isi: kalau ada titik dua (:), itu Map. {} kosong selalu dianggap Map, jadi set kosong wajib ditulis <String>{}.
Kesalahan umum
1. Set kosong tanpa anotasi tipe
// SALAH: dikira Set, ternyata Map
void main() {
var s = {};
// s.add('apel'); // ERROR: Map tidak punya add
print(s.runtimeType); // _Map<dynamic, dynamic>
}// BENAR
void main() {
var s = <String>{};
s.add('apel');
print(s); // {apel}
}2. Mengharapkan urutan seperti list
void main() {
var s = {'c', 'a', 'b'};
print(s); // {c, a, b}, urutan tidak dijamin bermakna!
// print(s[0]); // ERROR: Set tidak punya index []
}Set tidak mendukung akses index [0]. Kalau butuh elemen pertama, pakai .first. Kalau urutan penting, pertimbangkan list.
3. Objek custom dianggap duplikat secara salah
class Siswa {
String nama;
Siswa(this.nama);
}
void main() {
var s = <Siswa>{};
s.add(Siswa('Budi'));
s.add(Siswa('Budi')); // dianggap BEDA objek, keduanya masuk!
print(s.length); // 2
}Set memakai == dan hashCode untuk menilai duplikat. Objek custom yang tidak meng-override keduanya dianggap unik walau isinya sama. Solusinya dibahas di modul override equality.
Kesimpulan
Set = koleksi unik tanpa duplikat, ditulis dengan {}, dibuat kosong dengan <T>{}. Pakai untuk deduplikasi (.toSet()) dan pengecekan keanggotaan cepat. Ingat bedanya dengan Map literal.
Catatan teknis:
Set.containsberjalan dalam waktu konstan O(1) berkat hash table di baliknya, sedangkanList.containsmemindai satu per satu O(n). Untuk pengecekan berulang pada data besar, perbedaannya sangat terasa.
Tantangan
Pembersih duplikat
Diberi list email dengan duplikat, ubah jadi Set untuk dapat email unik, cetak jumlah uniknya, lalu ubah kembali jadi List.
void main() {
var email = ['[email protected]', '[email protected]', '[email protected]', '[email protected]', '[email protected]'];
// TODO
}