Idiomnya: map dengan nilai kosong
Go tidak punya kata kunci set dan tidak ada set di standard library. Map yang key-nya adalah elemen dan nilainya tidak membawa apa pun sudah cukup untuk tugas ini.
struct{} adalah tipe struct kosong dan struct{}{} adalah satu-satunya nilainya. Ukurannya nol byte, jadi map hanya menyimpan key dan tidak ada yang lain.
Semua aturan map berlaku: key harus bisa dibandingkan, urutan iterasinya acak, map nil memicu panic saat ditulis, dan penulisan concurrent butuh lock. Halaman map membahas masing-masing aturan itu.
map[T]struct{} atau map[T]bool
Cara penulisan lain yang umum adalah map[T]bool. Bentuk ini lebih enak dibaca karena key yang tidak ada mengembalikan false:
seen := map[string]bool{}
seen["a"] = true
if seen["a"] { ... }
Untung ruginya:
map[T]struct{} | map[T]bool | |
|---|---|---|
| Ukuran nilai | 0 byte | 1 byte (ditambah alignment) |
| Pemeriksaan keanggotaan | _, ok := s[k] | s[k] |
| Ambiguitas | tidak ada | s[k] = false adalah keadaan ketiga |
Baris ketiga adalah alasan sebenarnya banyak codebase lebih memilih struct kosong: dengan bool, suatu saat ada yang menulis s[k] = false dan len(s) tidak lagi sama dengan jumlah anggota. Untuk set yang kecil, perbedaan memorinya tidak berarti.
Menghapus duplikat dari slice
Pemakaian set yang paling umum adalah menghilangkan duplikat. Kode ini mempertahankan kemunculan pertama setiap nilai dan menjaga urutannya:
Output:
[b a c]
[3 1 2]
[1 2 3]
Union, intersection, difference
Aljabar set hanya butuh beberapa perulangan. Lakukan iterasi pada set yang lebih kecil saat menguji keanggotaan di set lainnya, karena setiap pencarian rata-rata berwaktu konstan.
sorted hanya ada supaya output-nya stabil. Mencetak set dengan range di atasnya memberikan urutan berbeda setiap kali dijalankan. maps.Keys dan slices.Sorted butuh Go 1.23.
Named type seperti type set map[string]struct{} tetap sebuah map: kamu mengaksesnya dengan indeks, melakukan range, dan delete dengan cara yang sama, dan kamu bisa menambahkan method padanya.
Set generic yang kecil
Dengan generics (Go 1.18), satu tipe mencakup setiap tipe elemen yang comparable. Ini sudah cukup untuk kebanyakan program:
Membungkus map di dalam struct menyembunyikan kerumitan struct{}{} dan menjamin map dibuat oleh constructor, sehingga panic karena map nil hilang. Sorted tidak bisa menjadi method: method tidak bisa mendeklarasikan type parameter sendiri atau memperketat constraint comparable milik tipenya, sedangkan pengurutan butuh cmp.Ordered. Halaman generics menjelaskan constraint.
Kalau kamu butuh set yang lengkap fiturnya (varian yang thread-safe, banyak operasi), ada package pihak ketiga seperti github.com/deckarep/golang-set. Untuk kebanyakan kode, idiom map atau tipe sepanjang 30 baris seperti ini sudah menjadi pilihan programmer Go.
Set berisi struct
Tipe apa pun yang comparable bisa menjadi elemen, termasuk struct yang field-nya comparable. Ini membuat pemeriksaan "apakah pasangan ini sudah pernah kulihat" menjadi langsung:
type edge struct{ from, to string }
visited := map[edge]struct{}{}
visited[edge{"a", "b"}] = struct{}{}
Slice dan map tidak bisa menjadi elemen set. Untuk mencatat slice yang unik, ubah dulu setiap slice menjadi key yang comparable, misalnya array berukuran tetap, atau string yang disusun dengan fmt.Sprint.
Kesalahan umum
- Lupa menginisialisasi.
var s map[string]struct{}bernilai nil; penambahan pertama akan panic. - Mencetak set dan mengharapkan output yang stabil. Urutkan anggotanya dulu.
- Memakai
map[T]boollalu menyimpanfalse. Akibatnyalentidak lagi menghitung jumlah anggota. Pakaideleteuntuk menghapus.
Pertanyaan yang sering diajukan
Apakah Go punya tipe set?
Tidak. Standard library tidak punya set. Idiomnya adalah map yang nilainya tidak membawa informasi apa pun: map[string]struct{}. Menambah elemen ditulis s[k] = struct{}{}, memeriksa keanggotaan dengan _, ok := s[k], menghapus dengan delete(s, k), dan ukurannya len(s).
Sebaiknya memakai map[T]bool atau map[T]struct{} untuk set di Go?
map[T]struct{} membuat maksudnya jelas dan nilainya berukuran nol byte. map[T]bool lebih enak dibaca (if seen[x]) karena key yang tidak ada mengembalikan false. Keduanya benar; perbedaan memorinya hanya berarti untuk set yang sangat besar. Pilih salah satu dan pakai secara konsisten.
Bagaimana cara menghapus duplikat dari slice di Go?
Untuk mempertahankan kemunculan pertama sesuai urutan, lakukan perulangan dan catat nilai yang sudah dilihat di map[T]struct{}, lalu append hanya nilai yang belum pernah dilihat. Kalau urutan tidak penting, urutkan lalu buang duplikat yang bersebelahan: slices.Sort(s); s = slices.Compact(s).