Map dan Set

Menggunakan map dan set sebagai tulang punggung data Clojure, termasuk varian terurut.

Daftar isi

Topik ini membahas map dan set, dua koleksi asosiatif yang menjadi tulang punggung penyusunan data di Clojure. Map menghubungkan kunci dengan nilai, sedangkan set menampung elemen unik tanpa duplikat. Topik ini juga membahas varian terurut, yaitu sorted-map dan sorted-set, yang menjamin urutan berdasarkan pembanding kuncinya. Setelah topik ini selesai, data aplikasi dapat dimodelkan sebagai map dan set dengan perilaku urutannya dipahami secara tepat.

Literal map dan akses nilainya#

Map ditulis dalam tanda kurung kurawal dengan pasangan kunci dan nilai yang berdampingan, seperti {:nama "Buku" :jumlah 12}. Keyword adalah pilihan kunci yang paling umum karena evaluasinya pasti dan pencariannya cepat. Akses nilai dilakukan dengan fungsi get atau dengan keyword itu sendiri yang berlaku sebagai fungsi, sehingga (:nama stok) dan (get stok :nama) menghasilkan nilai yang sama. Apabila kunci tidak ada, get mengembalikan nil atau nilai bawaan pada argumen ketiganya, dan jumlah pasangan dihitung dengan count.

clojure Contoh: akses nilai map
(def stok {:nama "Buku" :jumlah 12 :harga 35000})

; akses dengan get dan dengan keyword langsung
(println (:nama stok))
(println (get stok :jumlah))
(println (get stok :diskon 0))
(println (:penerbit stok))
(println (count stok))
text Keluaran
Buku
12
0
nil
3

Kecenderungan umum Clojure adalah memodelkan data sebagai map berkunci keyword, karena map mudah dibaca, mudah diurai, dan tidak memerlukan definisi tipe tambahan. Satu map mewakili satu entitas seperti barang di gudang atau satu catatan transaksi, sedangkan kumpulan entitas ditampung dalam vector yang elemennya berupa map. Pola ini terus digunakan pada seluruh jalur, sehingga kebiasaan membaca map penting dibangun pada tahap ini.

Mengubah map dengan assoc, dissoc, dan update#

Pengubahan map tidak pernah mengubah map aslinya, melainkan mengembalikan map baru, sejalan dengan sifat immutable koleksi Clojure. Fungsi assoc menambah pasangan baru atau mengganti nilai pada kunci yang sudah ada, dissoc menghapus pasangan berdasarkan kuncinya, dan update menerapkan fungsi pada nilai lama untuk menghasilkan nilai baru. Fungsi assoc dan dissoc menerima beberapa kunci sekaligus untuk perubahan yang mencakup lebih dari satu pasangan.

clojure Contoh: assoc, dissoc, dan update
; assoc menambah atau mengganti pasangan
(prn (assoc {:nama "Buku" :jumlah 12} :harga 35000))
(prn (assoc {:nama "Buku" :harga 35000} :harga 40000))

; dissoc menghapus pasangan
(prn (dissoc {:nama "Buku" :jumlah 12 :harga 35000} :jumlah))

; update menerapkan fungsi pada nilai lama
(prn (update {:jumlah 12} :jumlah + 3))
text Keluaran
{:nama "Buku", :jumlah 12, :harga 35000}
{:nama "Buku", :harga 40000}
{:nama "Buku", :harga 35000}
{:jumlah 15}

Fungsi update perlu dicermati karena bentuk argumennya berbeda dari dua lainnya. Setelah nama kunci, update menerima fungsi beserta argumen tambahannya, sehingga (update {:jumlah 12} :jumlah + 3) dibaca sebagai nilai baru sama dengan nilai lama ditambah tiga. Pola ini menghindari pembacaan dan penulisan ulang nilai secara manual, dan pola serupa digunakan di banyak fungsi inti lainnya. Karena map hasil adalah objek baru, map lama tetap sah digunakan bagian lain tanpa risiko berubah.

Set dan uji keanggotaan#

Set ditulis dalam tanda kurung kurawal yang diawali tanda pagar, seperti #{:budi :citra :dewi}, dan menampung setiap nilai paling banyak satu kali. Uji keanggotaan dilakukan dengan contains? yang mengembalikan boolean. Fungsi conj menambah elemen yang diabaikan apabila sudah ada, sehingga ukuran set tidak bertambah, sedangkan disj menghapus elemen yang ada. Set juga berlaku sebagai fungsi yang mengembalikan elemen itu sendiri apabila keanggotaannya ada dan nil apabila tidak.

clojure Contoh: keanggotaan dan pengubahan set
(def peserta #{:budi :citra :dewi})

; contains? menguji keanggotaan
(println (contains? peserta :budi))
(println (contains? peserta :ali))

; conj menambah elemen, elemen duplikat diabaikan
(prn (conj peserta :ali))
(println (count (conj peserta :citra)))

; disj menghapus elemen
(prn (disj peserta :dewi))
text Keluaran
true
false
#{:ali :dewi :citra :budi}
3
#{:citra :budi}

Set adalah pilihan alami untuk data keunikan, seperti daftar tag yang tidak boleh ganda atau daftar peserta yang tidak boleh tercatat dua kali. Pemilihan antara vector dan set mengikuti kebutuhan, yaitu vector apabila urutan dan duplikasi bermakna, sedangkan set apabila yang penting adalah keanggotaan. Operasi count pada set menghitung elemen uniknya, dan penambahan elemen yang sudah ada tidak menimbulkan perubahan apa pun, sebagaimana jumlah yang tetap tiga pada contoh.

Varian terurut: sorted-map dan sorted-set#

Varian hash dari map dan set tidak memberikan jaminan apa pun tentang urutan penyimpanan elemennya. Pada koleksi kecil, urutan penyisipan masih tampak terjaga karena detail implementasinya, tetapi mengandalkan perilaku itu membuat kode rapuh terhadap perubahan ukuran dan versi. Apabila urutan memang kebutuhan, Clojure menyediakan sorted-map dan sorted-set, yang menyimpan elemen menurut fungsi pembanding bawaan compare, yaitu urutan menaik untuk angka dan urutan kamus untuk string.

clojure Contoh: urutan pada varian terurut
; urutan hash-map tidak dijanjikan oleh kontrak Clojure
(prn {:c 3 :a 1 :b 2})

; sorted-set menampilkan elemen menurut urutan pembandingnya
(prn (sorted-set 3 1 2))
(prn (sorted-set "durian" "apel" "jeruk"))

; sorted-map menampilkan pasangan menurut urutan kuncinya
(prn (sorted-map :c 3 :a 1 :b 2))
(prn (sorted-map "kamis" 4 "senin" 1 "selasa" 2))
text Keluaran
{:c 3, :a 1, :b 2}
#{1 2 3}
#{"apel" "durian" "jeruk"}
{:a 1, :b 2, :c 3}
{"kamis" 4, "selasa" 2, "senin" 1}

Syarat penggunaan varian terurut adalah seluruh elemen dapat dibandingkan satu sama lain, karena pembanding compare dipanggil pada setiap penyisipan. Campuran tipe yang tidak sebanding memicu error saat koleksi dibangun, sehingga varian ini cocok untuk data yang tipenya seragam. Apabila urutan hanya diperlukan sesekali, fungsi sort atas koleksi biasa sudah cukup, sedangkan varian terurut dipilih apabila urutan harus terjaga pada setiap operasi penyisipan dan penghapusan.

Kesalahan umum#

Menulis dua pasangan dengan kunci sama pada literal map. Literal {:a 1 :a 2} memicu error Duplicate key saat dibaca, karena literal map wajib berisi kunci yang unik. Penyebabnya biasanya salah ketik pada kunci yang kembar. Perbaikannya dengan memeriksa penulisan kuncinya, atau menggunakan assoc apabila penggantian nilai memang dimaksudkan.

Mengandalkan urutan hash-map pada laporan atau keluaran. Urutan pasangan hash-map tidak dijanjikan kontraknya, sehingga laporan yang tersusun dari hash-map dapat tampil tak beraturan setelah datanya membesar. Perbaikannya dengan menggunakan sorted-map dan sorted-set apabila urutan menjadi kebutuhan permanen, atau menyortir sekali pada titik penyajian.

Mengira contains? menguji nilai. Fungsi ini menguji kunci atau indeks, sehingga (contains? [10 20 30] 2) bernilai true karena indeks 2 ada, bukan karena nilainya. Untuk menguji keberadaan sebuah nilai pada koleksi berurutan, gunakan pencarian berbasis nilai seperti some.

Ringkasan#

  • Map menyimpan pasangan kunci dan nilai, dengan keyword sebagai kunci paling umum dan akses lewat get maupun keyword langsung.
  • assoc, dissoc, dan update mengembalikan map baru tanpa mengubah map aslinya.
  • Set menampung elemen unik, diuji keanggotaannya dengan contains?, dan diubah dengan conj serta disj.
  • sorted-map dan sorted-set menjamin urutan menaik menurut compare, sedangkan varian hash tidak menjamin urutan apa pun.

Referensi#