Menu

Set di Golang: map[T]struct{}, Operasi Set, dan Set Generic

Go tidak punya tipe set bawaan. Idiom standarnya adalah map dengan nilai berupa struct kosong. Pelajari add, has, dan remove, union, intersection, dan difference, serta cara menulis Set generic yang kecil.

Halaman ini dilengkapi editor yang bisa dijalankan. Edit, jalankan, dan lihat output-nya seketika.

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 nilai0 byte1 byte (ditambah alignment)
Pemeriksaan keanggotaan_, ok := s[k]s[k]
Ambiguitastidak adas[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]bool lalu menyimpan false. Akibatnya len tidak lagi menghitung jumlah anggota. Pakai delete untuk 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).

Ilustrasi bahasa pemrograman di Coddy

Belajar coding dengan Coddy

Mulai