23. Map

Map menghubungkan kunci dengan nilai. Slice mencari nilai berdasarkan posisinya (0, 1, 2). Map mencarinya berdasarkan kunci, seperti saat kamu mencari kata di kamus.

Membuat map

Literal map adalah tipenya, diikuti pasangan kunci: nilai di dalam kurung kurawal:

stats := map[string]int{
    "level": 7,
    "hp":    95,
    "coins": 12,
}

Baca map[string]int sebagai "map dari string ke int". Semua kuncinya string, dan semua nilainya int. Kalau ditulis dalam beberapa baris, setiap pasangan butuh koma di belakangnya, termasuk yang terakhir. Tanpa koma terakhir itu, Go berhenti:

syntax error: unexpected newline in composite literal; possibly missing comma or }

Baca nilai dengan kurung siku:

stats := map[string]int{
    "level": 7,
    "hp":    95,
    "coins": 12,
}

fmt.Println(stats["level"]) // 7
fmt.Println(stats["hp"])    // 95

Satu map punya satu tipe nilai. Ini batasan nyata di Go, dan membentuk sisa bab ini. Nama adalah string dan level adalah int, jadi keduanya tidak muat dalam map yang sama:

player := map[string]int{
    "name":  "Keiko",
    "level": 7,
}
fmt.Println(player)
cannot use "Keiko" (untyped string constant) as int value in map literal

Untuk sekarang, simpan nilai dengan tipe berbeda di variabel atau map yang berbeda. Bab 26 memperkenalkan struct, yang menyatukan nilai dengan tipe berbeda.

Untuk memulai dengan map kosong dan mengisinya nanti, pakai make, yang kamu temui di Bab 22. len memberikan jumlah kunci:

stats := make(map[string]int)
stats["level"] = 1
fmt.Println(len(stats)) // 1

Map tidak punya penulisan dengan titik. stats.level tidak bisa dikompilasi, dan kamu selalu menulis stats["level"]:

stats.level undefined (type map[string]int has no field or method level)

Kalau kuncinya disimpan di variabel, itu juga bisa:

stats := map[string]int{"level": 7, "hp": 95}

field := "hp"
fmt.Println(stats[field]) // 95

Menambah dan mengubah nilai

Menugaskan ke kunci baru akan membuatnya. Menugaskan ke kunci yang sudah ada akan menggantinya. delete menghapusnya:

stats := map[string]int{"level": 7, "hp": 95, "coins": 12}

stats["mana"] = 30 // new key
stats["hp"] = 100  // replace existing
delete(stats, "coins")

fmt.Println(len(stats)) // 3

Setelah tiga baris itu, stats berisi "level", "hp", dan "mana". Menghapus kunci yang tidak ada tidak melakukan apa-apa. Contoh-contoh ini ada di exercises/23/01-map.go.

Perulangan dengan range

range memberimu setiap kunci dan nilai di dalam map:

for key, value := range stats {
    fmt.Println(key, value)
}

Map tidak punya urutan. Go mengunjungi kunci dalam urutan acak, dan urutannya berubah dari satu perulangan ke perulangan berikutnya. exercises/23/04-random-order.go mengulang map yang sama tiga kali, lalu mencetak nama yang sudah diurutkan. Tiga baris pertama pada output berikut berasal dari satu kali jalan yang sebenarnya, dan punyamu akan berbeda:

Ada Ben Cara Dan Eve
Eve Ada Ben Cara Dan
Dan Eve Ada Ben Cara
[Ada Ben Cara Dan Eve]

Jangan pernah menulis program yang outputnya bergantung pada urutan ini.

Kalau kamu butuh urutan yang tetap, kumpulkan kuncinya ke dalam slice, urutkan, lalu ulangi slice itu. slices.Sort, dari package slices, mengurutkan slice di tempat. Bab 24 membahas package itu. Tambahkan "slices" ke import-mu:

stats := map[string]int{"level": 7, "hp": 100, "mana": 30}

keys := []string{}
for key := range stats {
    keys = append(keys, key)
}
slices.Sort(keys)

for _, key := range keys {
    fmt.Println(key, stats[key])
}

Output, yang sama di setiap kali jalan:

hp 100
level 7
mana 30

Untuk melihat seluruh map dengan cepat, fmt.Println mencetaknya dengan kunci terurut. Ini hanya berlaku saat mencetak, bukan pada range:

stats := map[string]int{"level": 7, "hp": 100, "mana": 30}
fmt.Println(stats) // map[hp:100 level:7 mana:30]

Aman untuk memakai delete pada kunci yang sedang dikunjungi di dalam perulangan range.

Memeriksa apakah kunci ada

Meminta kunci yang tidak ada tidak membuat crash. Hasilnya adalah nilai nol dari tipe nilainya, yaitu 0 untuk int dan "" untuk string. Jadi kunci yang tidak ada bisa terlihat persis seperti 0 sungguhan:

stats := map[string]int{"level": 7, "hp": 0}

fmt.Println(stats["hp"])    // 0
fmt.Println(stats["email"]) // 0

Ini masalah yang sama dengan teks yang salah dan strconv.Atoi di Bab 21, dan jawabannya punya bentuk yang sama. Minta dua nilai. Yang kedua adalah bool, biasanya bernama ok, yang menyatakan apakah kuncinya ada:

stats := map[string]int{"level": 7, "hp": 0}

hp, ok := stats["hp"]
fmt.Println(hp, ok) // 0 true

mana, ok := stats["mana"]
fmt.Println(mana, ok) // 0 false

Pakai bersama if:

stats := map[string]int{"level": 7, "hp": 0}

coins, ok := stats["coins"]
if ok {
    fmt.Println("Coins:", coins)
} else {
    fmt.Println("No coins on file.")
}

Output:

No coins on file.

Map yang dideklarasikan dengan var tanpa nilai, seperti var stats map[string]int, belum siap dipakai. Membacanya memberikan nilai nol, tetapi menugaskan ke dalamnya membuat crash dengan panic: assignment to entry in nil map. Buat map dengan literal atau make, dan hal ini tidak akan terjadi.

Map dibagi kalau kamu memberikannya ke fungsi, sama seperti slice. Fungsi yang mengisi sebuah kunci juga mengisinya untuk pemanggil, tanpa perlu mengembalikan apa-apa:

func addCoins(stats map[string]int, amount int) {
    stats["coins"] += amount
}

func main() {
    stats := map[string]int{"coins": 5}
    addCoins(stats, 10)
    fmt.Println(stats["coins"]) // 15
}

Map bersarang

Nilai map bisa berupa map lain, atau slice. Tipenya menyatakan hal itu:

positions := map[string]map[string]int{
    "Keiko": {"x": 0, "y": 5, "z": -3},
}
inventories := map[string][]string{
    "Keiko": {"sword", "potion", "map"},
}

fmt.Println(positions["Keiko"]["x"]) // 0
fmt.Println(inventories["Keiko"][0]) // sword

map[string]map[string]int dibaca "map dari string ke map dari string ke int". Di dalam literal, Go mengizinkan kamu tidak menulis tipe bagian dalam, jadi {"x": 0, ...} sudah cukup.

Untuk memanjangkan slice yang disimpan di map, tugaskan hasil append kembali ke kunci itu:

inventories["Keiko"] = append(inventories["Keiko"], "rope")

Contoh-contoh ini ada di exercises/23/02-nested.go.

Map dan slice bersama-sama

Sebuah quest punya nama, hadiah, dan daftar langkah. Itu tiga tipe yang berbeda, dan satu map tidak bisa menampungnya. Cara yang bisa dipakai pada tahap ini adalah dua map dengan kunci yang sama, satu map untuk setiap jenis informasi:

questSteps := map[string][]string{
    "Healer's Errand": {
        "Speak to the elder",
        "Collect 5 herbs",
        "Return to camp",
    },
}
questRewards := map[string]int{
    "Healer's Errand": 50,
}

name := "Healer's Errand"

fmt.Println(questRewards[name])    // 50
fmt.Println(questSteps[name][0])   // Speak to the elder
fmt.Println(len(questSteps[name])) // 3

Ini adalah exercises/23/03-mixed.go. Mini-proyek Bagian 5 membangun seluruh dunia game-nya dengan cara ini. Cara ini berjalan, tetapi tidak ada yang menjaga kedua map tetap selaras. Kalau kamu menambahkan quest ke satu map dan lupa pada yang lain, Go tidak akan memberi tahu. Bab 26 memperbaikinya.

PR

Starter PR ada di exercises/23/homework/. Setiap starter bisa langsung dijalankan dan mencetak satu baris pengganti.

Soal 1 — Catatan pemain

Buka exercises/23/homework/01-player-record.go. Buat map[string]string bernama player dengan setidaknya empat kunci ini: "name", "class", "level", "alive". Semua nilainya berupa teks, seperti "7" dan "true", karena satu map punya satu tipe nilai. Lalu cetak setiap kunci dan nilai, satu per baris, dengan urutan kunci yang terurut.

Soal 2 — Berat inventaris

Buka exercises/23/homework/02-inventory-weights.go. Sebuah map menghubungkan nama barang dengan beratnya. Hitung dan cetak total berat dengan dua desimal.

Soal 3 — Pencarian aman

Buka exercises/23/homework/03-safe-lookup.go. Tulis fungsi lookup(player map[string]string, key string) string yang mengembalikan nilainya kalau kuncinya ada, dan string "(not found)" kalau tidak. Uji dengan tiga panggilan: satu yang ketemu, satu yang tidak ketemu, dan satu yang kuncinya disimpan di variabel.

Tantangan — Frekuensi kata

Buka exercises/23/homework/04-word-frequency.go. Dari slice berisi kata (sebagian berulang), buat map dari setiap kata unik ke jumlahnya. Cetak satu pasangan per baris, dengan urutan kata yang terurut.

Stuck atau sudah selesai? Buka halaman solusi PR.