Pendahuluan
Salah satu struktur data paling berguna dalam ilmu komputer adalah tabel hash. Kebanyakan implementasi tabel hash memiliki banyak properti, tetapi secara umum mereka memiliki kecepatan pencarian, penambahan, dan penghapusan. Go menyediakan tipe map bawaan yang mengimplementasikan sebuah tabel hash.
Deklarasi dan inisiasi
Sebuah tipe map pada Go bentuknya seperti berikut:
map[TipeKey]TipeNilai
yang mana TipeKey
bisa tipe apa saja yang dapat
dibandingkan
(lebih lanjut akan kita bahas nanti), dan TipeNilai
yang bisa bertipe apa
pun, termasuk map juga!
Variabel m
berikut adalah sebuah map dengan kunci bertipe string
dan
nilai bertipe int
:
var m map[string]int
Tipe map adalah tipe referensi, seperti pointer atau slice, sehingga nilai
dari variabel m
di atas adalah nil
;
ia tidak menunjuk ke map yang telah diinisiasi.
Sebuah map yang nil
bersifat seperti map kosong saat pembacaan, namun
mencoba menulis ke sebuah map yang nil
akan menyebabkan panic;
maka dari itu jangan pernah melakukan penulisan ke map yang belum diinisiasi.
Untuk menginisiasi map, gunakan fungsi bawaan make
:
m = make(map[string]int)
Fungsi make
membuat alokasi dan menginisiasi sebuah struktur data map hash
dan mengembalikan sebuah nilai map yang menunjuk ke hash tersebut.
Spesifikasi dari struktur data tersebut adalah detail implementasi dari
runtime.
Dalam artikel ini kita akan fokus pada penggunaan map, bukan implementasinya.
Menggunakan map
Go menyediakan sintaksis umum untuk bekerja dengan map.
Perintah berikut men-set kunci "route" untuk nilai 66
:
m["route"] = 66
Perintah berikut mengambil nilai yang disimpan dengan kunci "route" dan menyimpannya dalam sebuah variabel baru:
i := m["route"]
Jika kunci tidak ada, kita akan mendapatkan sebuah nilai kosong.
Pada kasus ini secara tipe dari nilai adalah int
, maka nilai kosongnya
adalah 0:
j := m["root"] // j == 0
Fungsi bawaan len
mengembalikan jumlah item dalam sebuah map:
n := len(m)
Fungsi bawaan delete
menghapus sebuah item dalam map:
delete(m, "route")
Fungsi delete
tidak mengembalikan nilai, dan akan tetap sukses bila kunci
yang diberikan tidak ada dalam map.
Penempatan dua-nilai memeriksa keberadaan dari sebuah kunci:
i, ok := m["route"]
Pada perintah tersebut, nilai yang pertama (i
) diisi dengan nilai yang
disimpan dalam kunci "route".
Jika kunci tersebut tidak ada, maka nilai i
akan kosong (0).
Nilai yang kedua (ok
) adalah sebuah tipe bool
yang akan true
jika kunci
ada dalam map, dan false
jika tidak ada.
Untuk memeriksa sebuah kunci ada atau tidak tanpa mengambil nilainya, gunakan karakter garis-bawah pada nilai pertama:
_, ok := m["route"]
Untuk mengiterasi isi dari sebuah map, gunakan range
:
for key, value := range m { fmt.Println("Key:", key, "Value:", value) }
Untuk menginisiasi map dengan beberapa data, gunakan literal map:
commits := map[string]int{ "rsc": 3711, "r": 2138, "gri": 1908, "adg": 912, }
Sintaksis yang sama dapat digunakan untuk menginisiasi map kosong, yang secara
fungsionalitas identik dengan fungsi make
:
m = map[string]int{}
Eksploitasi nilai kosong
Telah kita ketahui bahwa pembacaan sebuah nilai dengan kunci yang tidak ada pada map akan mengembalikan sebuah nilai kosong.
Misalnya, sebuah map dengan nilai boolean dapat digunakan untuk struktur data
set (ingatlah bahwa nilai kosong dari tipe boolean adalah false
).
Contoh berikut mengiterasi linked list dari Node
dan mencetak nilainya.
Ia menggunakan sebuah map dengan kunci berupa pointer ke Node untuk memeriksa
apakah Node pernah dikunjungi dari dalam daftar.
type Node struct { Next *Node Value interface{} } var first *Node visited := make(map[*Node]bool) for n := first; n != nil; n = n.Next { if visited[n] { fmt.Println("cycle detected") break } visited[n] = true fmt.Println(n.Value) }
Ekspresi visited[n]
bernilai true
jika n
pernah dikunjungi, atau false
jika n
tidak ada.
Tidak perlu menggunakan bentuk penempatan dua-nilai untuk memeriksa keberadaan
n
dalam map;
nilai kosong bawaan dari tipe bool
telah melakukan hal tersebut.
Contoh penggunaan nilai kosong lainnya yaitu map dari slice.
Menambahkan sebuah item ke dalam slice yang nil akan mengalokasikan slice yang
baru, sehingga cukup satu baris untuk menambahkan sebuah nilai ke dalam sebuah
map dari slice;
tidak perlu memeriksa apakah kunci ada atau tidak.
Pada contoh berikut, variabel slice people
diisi dengan nilai Person
.
Setiap Person
memiliki Name
dan slice Likes
.
Contoh ini membuat sebuah map untuk mengasosiasikan setiap like dengan
daftar orang yang menyukainya.
type Person struct { Name string Likes []string } var people []*Person likes := make(map[string][]*Person) for _, p := range people { for _, l := range p.Likes { likes[l] = append(likes[l], p) } }
Untuk mencetak daftar orang yang menyukai "cheese":
for _, p := range likes["cheese"] { fmt.Println(p.Name, "likes cheese.") }
Untuk mencetak jumlah orang yang menyukai "bacon":
fmt.Println(len(likes["bacon"]), "people like bacon.")
Ingat lah bahwa secara range
dan len
menganggap slice yang nil sebagai
slice dengan panjang 0, kedua contoh tersebut akan berjalan walaupun tidak ada
orang (dalam variabel people
) yang menyukai "cheese" atau "bacon".
Tipe-tipe kunci dari map
Seperti yang dibahas sebelumnya, kunci dari map bisa berupa tipe apa pun yang
dapat dibandingkan.
Spesifikasi bahasa
mendefinisikan hal ini lebih jelas, namun singkatnya, tipe-tipe yang dapat
dibandingkan yaitu boolean, numeric, string, pointer, channel, interface, dan
struct atau array yang berisi hanya tipe tersebut.
Berarti tipe yang tidak dapat dibandingkan yaitu slice, map, dan fungsi;
tipe-tipe tersebut tidak dapat dibandingkan lewat operator ==
, dan tidak
bisa digunakan sebagai kunci dari map.
Kalau tipe seperti string, int, dan tipe dasar lainnya cukup jelas kenapa bisa digunakan sebagai kunci dari map, namun yang mungkin kurang jelas adalah penggunakan struct sebagai kunci. Struct dapat digunakan sebagai kunci data dengan banyak dimensi. Contohnya, map dari map berikut dapat digunakan untuk menghitung kunjungan halaman web berdasarkan negara:
hits := make(map[string]map[string]int)
Map tersebut yaitu map dari string ke (map dari string ke int). Kunci dari map bagian luar adalah path ke sebuah halaman web dengan nilainya adalah sebuah map sendiri. Kunci dari map bagian dalam adalah dua-huruf kode negara dengan nilai dari map yaitu jumlah kunjungan. Ekspresi berikut mengambil jumlah kunjungan halaman "/doc" dari negara Australia:
n := hits["/doc/"]["au"]
Sayangnya, pendekatan seperti ini menjadi sukar pada saat menambah data, untuk setiap kunci bagian luar, kita harus memeriksa apakah map bagian dalam telah diinisiasi atau belum, dan menginisiasi-nya bila diperlukan:
func add(m map[string]map[string]int, path, country string) { mm, ok := m[path] if !ok { mm = make(map[string]int) m[path] = mm } mm[country]++ } add(hits, "/doc/", "au")
Di sisi lain, pendekatan dengan menggunakan struct sebagai kunci mempermudah semua hal tersebut:
type Key struct { Path, Country string } hits := make(map[Key]int)
Saat seseorang dari Vietnam mengunjungi halaman depan ("/"), meningkatkan nilai (dan juga membuat nilai baru) penghitung menjadi satu-baris saja:
hits[Key{"/", "vn"}]++
Begitu juga, cukup mudah untuk melihat berapa banyak orang dari Swiss yang telah membaca halaman spesifikasi ("/ref/spec"):
n := hits[Key{"/ref/spec", "ch"}]
Konkurensi
Map tidak aman digunakan secara konkuren: tidak didefinisikan apa yang akan terjadi bila kita membaca dan menulis pada map yang sama secara simultan. Jika kita harus membaca dan menulis ke sebuah map dari goroutine yang berbeda, akses ke map tersebut harus di-mediasi oleh sebuah mekanisme sinkronisasi. Salah satu cara umum untuk melindungi map yaitu dengan sync.RWMutex.
Perintah berikut mendeklarasikan sebuah variabel counter
bertipe struct
anonim yang berisi sebuah map dan menanam sync.RWMutex
.
var counter = struct{ sync.RWMutex m map[string]int }{m: make(map[string]int)}
Untuk membaca dari nilai map dari counter
, gunakan pengunci baca:
counter.RLock() n := counter.m["some_key"] counter.RUnlock() fmt.Println("some_key:", n)
Untuk menulis ke counter
, gunakan pengunci tulis:
counter.Lock() counter.m["some_key"]++ counter.Unlock()
Urutan iterasi
Saat mengiterasi sebuah map lewat pengulangan range
, urutan iterasi tidak
menentu dan tidak dijamin sama dari satu iterasi ke iterasi selanjutnya.
Jika Anda membutuhkan iterasi yang stabil, Anda harus menyimpan sebuah
struktur data terpisah yang menentukan urutan kunci.
Contoh berikut menggunakan slice sebagai urutan kunci untuk mencetak sebuah
map[int]string
secara terurut:
import "sort" var m map[int]string var keys []int for k := range m { keys = append(keys, k) } sort.Ints(keys) for _, k := range keys { fmt.Println("Key:", k, "Value:", m[k]) }