Map, Set, dan dict adalah struktur pencarian yang memetakan kunci ke nilai atau menyimpan sekumpulan anggota unik. Topik ini membahas Map dan Set yang imutabel beserta modulnya, dict sebagai pembungkus IDictionary untuk keperluan interop, Dictionary dari .NET sebagai kamus yang isinya dapat berubah, dan pertimbangan pemilihan di antara semuanya. Contoh menggunakan rekap nilai ujian dan jadwal piket yang seluruh keluarannya berasal dari eksekusi nyata.
Map untuk pasangan kunci dan nilai#
Map menyimpan pasangan kunci dan nilai (key-value) dengan setiap kunci muncul paling banyak satu kali. Map dibuat dari list pasangan melalui Map.ofList, dan seluruh operasinya berada pada modul Map. Kunci boleh bertipe apa pun yang dapat dibandingkan, termasuk tuple maupun record, karena Map membandingkan kunci secara struktural untuk menata posisinya.
// Map kunci-nilai imutabel untuk nilai ujian per mahasiswa
let nilai = Map.ofList [ ("Ali", 80); ("Budi", 65); ("Citra", 90) ]
// penelusuran Map.toSeq menghasilkan entri terurut menurut kunci
nilai
|> Map.toSeq
|> Seq.map (fun (kunci, v) -> sprintf "%s: %d" kunci v)
|> String.concat ", "
|> printfn "isi map : %s"
printfn "nilai Ali : %d" (Map.find "Ali" nilai)
printfn "nilai Dewi : %A" (Map.tryFind "Dewi" nilai)
// Map.add menghasilkan map baru, map lama tetap utuh
let nilaiBaru = Map.add "Dewi" 75 nilai
printfn "banyak entri map lama : %d" nilai.Count
printfn "banyak entri map baru : %d" nilaiBaru.Count
printfn "map lama berisi Dewi : %b" (Map.containsKey "Dewi" nilai)
// Map.remove juga menghasilkan map baru
let sisaBudi = Map.remove "Budi" nilaiBaru
printfn "map baru berisi Budi : %b" (Map.containsKey "Budi" sisaBudi)
printfn "entri Citra tetap ada : %d" (Map.find "Citra" sisaBudi)isi map : Ali: 80, Budi: 65, Citra: 90
nilai Ali : 80
nilai Dewi : None
banyak entri map lama : 3
banyak entri map baru : 4
map lama berisi Dewi : false
map baru berisi Budi : false
entri Citra tetap ada : 90Baris pertama menampilkan entri terurut menurut kuncinya karena Map menyusun isinya dalam struktur pohon, sehingga penelusuran Map.toSeq menghasilkan urutan kunci menaik dan bukan urutan penulisan. Pencarian dengan Map.find mengembalikan nilainya langsung, sedangkan Map.tryFind mengembalikan tipe Option, yaitu nilai yang mungkin ada berupa Some atau tidak ada berupa None. Pada contoh, kunci Dewi belum terdaftar sehingga hasilnya None, dan pencarian yang aman seperti ini diulas lebih lanjut pada modul khusus tipe Option. Baris Map.add "Dewi" 75 nilai menambah entri baru tanpa mengubah map lama, yang terbaca dari bertambahnya jumlah entri dan containsKey bernilai false pada map lama, sehingga kedua versi dapat digunakan berdampingan. Map.remove bekerja dengan cara yang sama dan menghasilkan versi tanpa kunci yang dihapus, sedangkan entri lain tetap dapat dicari pada versi barunya.
Set untuk himpunan tanpa duplikat#
Set menyimpan sekumpulan anggota unik tanpa memperhatikan urutan penulisan dan banyaknya pengulangan. Set dibuat dari list melalui Set.ofList, anggotanya dibandingkan secara struktural, dan tampilannya terurut menurut nilai anggota. Keanggotaan satu nilai diperiksa dengan Set.contains, sehingga pertanyaan sederhana berupa apakah satu nama terdaftar dapat dijawab tanpa menelusuri list secara manual. Operasi himpunan tersedia sebagai operator maupun fungsi modul, sehingga hubungan antar himpunan dapat dituliskan secara ringkas.
// Set jadwal piket: duplikat otomatis disatukan
let piketSenin = Set.ofList [ "budi"; "ali"; "citra"; "budi" ]
printfn "%A" piketSenin
printfn "banyak anggota : %d" (Set.count piketSenin)
let piketSelasa = Set.ofList [ "budi"; "dewi" ]
printfn "gabungan : %A" (piketSenin + piketSelasa)
printfn "irisan : %A" (Set.intersect piketSenin piketSelasa)
printfn "selisih : %A" (piketSenin - piketSelasa)
// menambah anggota yang sudah ada menghasilkan himpunan yang sama
printfn "tambah ulang : %A" (Set.add "ali" piketSenin)set ["ali"; "budi"; "citra"]
banyak anggota : 3
gabungan : set ["ali"; "budi"; "citra"; "dewi"]
irisan : set ["budi"]
selisih : set ["ali"; "citra"]
tambah ulang : set ["ali"; "budi"; "citra"]List awal menuliskan budi dua kali, tetapi set hasilnya hanya memuatnya satu kali, dan Set.count mengonfirmasi jumlah anggotanya tiga. Operator + menggabungkan dua himpunan, Set.intersect mengambil anggota yang ada di keduanya, sedangkan operator - mengambil anggota himpunan kiri yang tidak ada di kanan. Baris terakhir menambahkan anggota yang sudah terdaftar dan menghasilkan himpunan yang identik, karena Set menjamin keunikan anggotanya. Seperti Map, setiap operasi menghasilkan set baru dan set lama tetap utuh, sehingga pola pembaruannya sama dengan koleksi imutabel lainnya. Kunci-kunci pada sebuah Map juga dapat diturunkan menjadi Set melalui Map.toSeq, Seq.map fst, dan Set.ofSeq, sehingga data yang sama dapat melayani kebutuhan pencarian sekaligus kebutuhan himpunan tanpa menyimpan dua sumber kebenaran.
dict dan Dictionary di tepi .NET#
Banyak pustaka .NET menerima dan mengembalikan antarmuka IDictionary<'K,'V>, dan F# menyediakan dua jalur untuk berhadapan dengannya. Fungsi dict membungkus list pasangan kunci-nilai menjadi IDictionary yang isinya tidak dapat diubah, cocok untuk menyediakan data baca-saja kepada pustaka .NET. Sebaliknya, kelas System.Collections.Generic.Dictionary<'K,'V> adalah kamus mutable yang isinya bertambah dan berganti selama program berjalan, dan penggantian lewat indeks pada kunci baru justru menambahkan entri tersebut.
// dict membungkus pasangan kunci-nilai menjadi IDictionary baca-saja
let belanja = dict [ ("beras", 1); ("telur", 12) ]
printfn "telur : %d" belanja.["telur"]
printfn "%b" (belanja.ContainsKey("beras"))
// Dictionary adalah kamus .NET yang isinya dapat berubah
let stok = System.Collections.Generic.Dictionary<string, int>()
stok.Add("beras", 1)
stok.["telur"] <- 12
stok.["beras"] <- 5
printfn "beras : %d" stok.["beras"]
// urutan enumerasinya tidak dijamin, urutkan sebelum dicetak
let rapih =
stok
|> Seq.sortBy (fun kv -> kv.Key)
|> Seq.map (fun kv -> sprintf "%s: %d" kv.Key kv.Value)
|> String.concat ", "
printfn "%s" rapihtelur : 12
true
beras : 5
beras: 5, telur: 12Pembacaan belanja.["telur"] bekerja seperti biasa, tetapi penulisan belanja.["telur"] <- 0 ditolak pada waktu berjalan karena pembungkus dict memang tidak menyediakan pengubahan. Kelas Dictionary tidak membatasi hal itu, dan baris stok.["beras"] <- 5 mengganti entri yang sebelumnya dibuat oleh stok.Add. Urutan enumerasi kamus semacam ini tidak dijamin oleh .NET, sehingga contoh mengurutkannya dengan Seq.sortBy terlebih dahulu sebelum dicetak, dan kebiasaan yang sama berlaku setiap kali isi kamus mutable perlu ditampilkan secara stabil. Kedua bentuk tersebut mengimplementasikan IDictionary<'K,'V>, sehingga keduanya dapat diteruskan ke API .NET yang menuntut antarmuka itu.
Memilih struktur pencarian#
Pertimbangan utama pemilihan struktur pencarian ada dua, yaitu kebutuhan mutasi dibandingkan kebutuhan versi, dan sifat tipe kuncinya. Map dan Set menampung data yang berkembang sebagai rangkaian versi, sedangkan Dictionary menampung keadaan yang diperbarui di tempat. Untuk kunci, Map dan Set menuntut tipe yang dapat dibandingkan secara struktural, sedangkan dict dan Dictionary mengandalkan hash serta kesetaraan .NET pada kuncinya.
| Kebutuhan | Pilihan yang tepat | Alasan |
|---|---|---|
| Kunci-nilai yang versi lamanya masih diperlukan | Map | imutabel dan persisten, Map.add menghasilkan versi baru tanpa menyentuh versi lama |
| Anggota unik dengan operasi himpunan | Set | gabungan, irisan, dan selisih tersedia sebagai operator bawaan |
| Isi yang bertambah dan berganti sepanjang program berjalan | Dictionary | mutable, penggantian lewat indeks tanpa menyalin seluruh isi |
Data diteruskan ke API .NET yang menuntut IDictionary | dict atau Dictionary | keduanya mengimplementasikan IDictionary<'K,'V> |
Pada kebanyakan kode F#, Map menjadi pilihan baku untuk pencarian kunci-nilai karena imutabilitasnya sejalan dengan gaya penulisan lainnya di jalur ini. Dictionary digunakan apabila penghitungan berjalan terus-menerus dan penyalinan versi menjadi sia-sia, misalnya penghitung yang naik pada setiap baris masukan. dict berperan terbatas sebagai penyedia data baca-saja di tepi pustaka .NET, bukan sebagai koleksi kerja sehari-hari. Apabila pilihan awal ternyata kurang tepat, seluruh struktur di tabel di atas dapat dikonversi satu sama lain dengan fungsi konversi seperti Map.toList dan dict, sehingga keputusan awal tidak mengunci bentuk data secara permanen.
Ringkasan#
- Map menyimpan pasangan kunci-nilai secara imutabel dan terurut, dengan
tryFindsebagai pencarian yang tidak melempar exception pada kunci yang hilang. - Set menyimpan anggota unik dan menyediakan gabungan, irisan, serta selisih sebagai operasi bawaan.
dictmembungkus data menjadiIDictionarybaca-saja untuk interop, sedangkanDictionaryadalah kamus mutable dari .NET untuk isi yang terus berubah.- Pemilihannya ditentukan oleh kebutuhan mutasi atau versi serta sifat tipe kunci, yaitu perbandingan struktural untuk Map dan Set, hash .NET untuk dict dan Dictionary.
Referensi#
- F# collection types — Microsoft Learn — Ringkasan koleksi bawaan F#, termasuk Map, Set, dan bentuk antarmukanya dengan .NET.
- F# Language Reference — Microsoft Learn — Rujukan induk sintaks bahasa F# yang menaungi seluruh topik di jalur ini.