22. Slice

Slice menampung deretan nilai yang berurutan, dan bisa bertambah panjang atau memendek. Kamu sudah bertemu dengannya di Bab 14, ketika strings.Fields dan strings.Split memberimu daftar kata. Bab ini menunjukkan cara membuat, mengubah, dan menelusurinya.

Membuat slice

Literal slice adalah tipenya, []string, diikuti nilai-nilainya di dalam kurung kurawal:

fruits := []string{"apple", "banana", "cherry"}
fmt.Println(fruits[0]) // apple
fmt.Println(fruits[1]) // banana
fmt.Println(fruits[2]) // cherry

Baca []string sebagai "slice berisi string". [] ditulis lebih dulu, lalu tipe setiap item. Semua item dalam slice punya satu tipe itu, jadi mencampurnya tidak bisa dikompilasi:

mixed := []int{1, "two", 3}
fmt.Println(mixed)
cannot use "two" (untyped string constant) as int value in array or slice literal

Penting: slice dihitung dari 0. Item pertama ada di posisi 0, bukan 1, seperti yang sudah kamu ketahui dari string di Bab 12. Hampir semua pemula pernah tersandung sekali karena hal ini.

  • fruits[0] adalah "apple", item pertama.
  • fruits[1] adalah "banana", item kedua.
  • fruits[2] adalah "cherry", item ketiga.

Pakai penulisan yang sama untuk mengubah item, dan fmt.Println untuk melihat seluruh slice:

fruits := []string{"apple", "banana", "cherry"}
fruits[1] = "blueberry"
fmt.Println(fruits) // [apple blueberry cherry]

Slice dengan tiga item punya posisi yang sah 0, 1, dan 2. Posisi 3 tidak ada, dan memintanya membuat program crash saat berjalan:

fruits := []string{"apple", "banana", "cherry"}
position := 3
fmt.Println(fruits[position])
panic: runtime error: index out of range [3] with length 3

Ini jenis crash yang sama dengan di Bab 12 dan 17: program berhasil dikompilasi, lalu berhenti.

Panjang dengan len

len memberikan jumlah item, seperti pada string:

fruits := []string{"apple", "banana", "cherry"}
fmt.Println(len(fruits))           // 3
fmt.Println(fruits[len(fruits)-1]) // cherry

Posisi sah terakhir selalu len(fruits)-1. Go tidak punya posisi negatif, jadi begitulah cara mencapai item terakhir. fruits[-1] tidak bisa dikompilasi:

invalid argument: index -1 (constant of type int) must not be negative

Slice juga bisa kosong. Menulis []string{} membuatnya, dan kamu akan sering memulai dari sini:

empty := []string{}
fmt.Println(len(empty)) // 0
fmt.Println(empty)      // []

Perulangan atas slice

Bab 18 memakai range untuk menghitung. range juga menelusuri slice, satu item setiap kali.

Nilainya saja

range memberimu dua hal di setiap putaran, posisi dan nilai. Kalau kamu hanya ingin nilainya, tulis _ di tempat posisi:

for _, fruit := range fruits {
    fmt.Println(fruit)
}

Output:

apple
banana
cherry

Perulangan berhenti sendiri setelah item terakhir, dan pada slice kosong isinya tidak pernah berjalan. Kamu tidak bisa membiarkan posisi tidak terpakai begitu saja, karena setiap variabel yang kamu deklarasikan harus dipakai:

i declared and not used

Posisi dan nilai bersama-sama

Kalau kamu juga butuh posisinya, beri nama. Posisi dimulai dari 0:

for i, fruit := range fruits {
    fmt.Println(i, fruit)
}

Output:

0 apple
1 banana
2 cherry

Untuk nomor yang dimulai dari 1, misalnya daftar bernomor untuk pengguna, tambahkan 1 saat mencetak:

for i, fruit := range fruits {
    fmt.Printf("%d. %s\n", i+1, fruit)
}

Output:

1. apple
2. banana
3. cherry

Kalau kamu hanya ingin posisinya, tulis for i := range fruits.

Variabel nilai menyimpan salinan item. Mengubahnya tidak mengubah slice. Untuk mengubah item, pakai posisinya:

scores := []int{1, 2, 3}

for _, score := range scores {
    score = score * 10
    fmt.Println(score)
}
fmt.Println(scores)

for i := range scores {
    scores[i] = scores[i] * 10
}
fmt.Println(scores)

Output:

10
20
30
[1 2 3]
[10 20 30]

Buka exercises/22/01-slice-loop.go. Tambahkan buah keempat dan jalankan lagi. len(fruits) dan perulangannya menyesuaikan tanpa perubahan lain.

Slice dari teks

Sekarang slice dari Bab 14 berguna. strings.Fields memisahkan pada spasi sepanjang apa pun, dan strings.Split memisahkan pada teks yang kamu berikan:

words := strings.Fields("go north   now")
fmt.Println(len(words)) // 3
for _, word := range words {
    fmt.Println(word)
}

parts := strings.Split("red,green,blue", ",")
for i, part := range parts {
    fmt.Println(i, part)
}

Ini adalah exercises/22/05-from-text.go. Program ini mencetak:

3
go
north
now
0 red
1 green
2 blue

Menambah dan menghapus item

Slice tidak bertambah panjang kalau kamu menugaskan nilai melewati ujungnya. Coba di exercises/22/04-past-the-end.go, yang menugaskan ke fruits[3] pada slice berisi tiga item. Program ini berhasil dikompilasi, lalu crash:

3
panic: runtime error: index out of range [3] with length 3

Untuk menambah di akhir, pakai append. Fungsi ini menerima slice dan nilai baru, lalu mengembalikan slice yang lebih panjang. Kamu harus menyimpan hasilnya, biasanya dengan menugaskannya kembali ke variabel yang sama:

fruits := []string{"apple", "banana"}

fruits = append(fruits, "cherry")
fmt.Println(fruits[len(fruits)-1]) // cherry
fmt.Println(len(fruits))           // 3

Tanpa penugasan, program tidak bisa dikompilasi:

fruits := []string{"apple", "banana"}
append(fruits, "cherry")
fmt.Println(fruits)
append(fruits, "cherry") (value of type []string) is not used

Menugaskan hasilnya ke variabel lain bisa dikompilasi, dan slice aslinya tetap seperti semula:

fruits := []string{"apple", "banana"}
more := append(fruits, "cherry")
fmt.Println(fruits) // [apple banana]
fmt.Println(more)   // [apple banana cherry]

Menambah di depan butuh satu langkah lagi. Buat slice baru berisi satu item, lalu tambahkan slice lama kepadanya. ... setelah slice memberikan item-itemnya satu per satu, seolah-olah kamu mengetiknya:

fruits := []string{"apple", "banana", "cherry"}

fruits = append([]string{"apricot"}, fruits...)
fmt.Println(fruits[0]) // apricot
fmt.Println(fruits[1]) // apple

Untuk menghapus item di ujung, pakai pemotongan dari Bab 12. Cara kerjanya pada slice sama dengan pada string, dan sekali lagi kamu menugaskan hasilnya kembali:

fruits := []string{"apricot", "apple", "banana", "cherry"}

fruits = fruits[:len(fruits)-1]
fmt.Println(fruits) // [apricot apple banana]

fruits = fruits[1:]
fmt.Println(fruits) // [apple banana]

Untuk menghapus dari tengah, simpan bagian sebelum item dan tambahkan bagian sesudahnya:

fruits := []string{"apple", "banana", "cherry", "date"}

i := 1
fruits = append(fruits[:i], fruits[i+1:]...)
fmt.Println(fruits) // [apple cherry date]

Awalnya ini terlihat aneh, jadi baca per bagian. fruits[:i] adalah semua yang sebelum posisi i. fruits[i+1:] adalah semua yang sesudahnya. append menggabungkan keduanya, tanpa posisi i. Contoh-contoh ini ada di exercises/22/02-insert-remove.go.

Jangan mengisi item dengan "" untuk "menghapusnya", seperti fruits[1] = "". Itu mengganti nilainya tetapi mempertahankan posisinya, sehingga ada lubang di slice-mu. len tetap menghitungnya.

Slice berbagi item

Slice berperilaku berbeda dari angka dan string dalam satu hal penting: menugaskan slice ke variabel lain tidak menyalin item-itemnya.

a := []int{1, 2, 3}
b := a
b[0] = 99
fmt.Println(a[0]) // 99

a dan b menamai item yang sama. Untuk salinan sungguhan, buat slice baru dengan panjang yang tepat memakai make, lalu isi dengan copy:

a := []int{99, 2, 3}

c := make([]int, len(a))
copy(c, a)
c[0] = 0
fmt.Println(a[0]) // 99
fmt.Println(c[0]) // 0

make([]int, len(a)) membuat slice berisi nol sebanyak itu. copy lalu menyalin item dari argumen keduanya ke argumen pertamanya.

Berbagi yang sama terjadi kalau kamu memberikan slice ke fungsi. Fungsi yang mengubah item juga mengubahnya untuk pemanggil. Parameter int tidak berperilaku seperti ini, seperti kata Bab 21:

func doubleAll(numbers []int) {
    for i := range numbers {
        numbers[i] = numbers[i] * 2
    }
}

func main() {
    scores := []int{10, 20}
    doubleAll(scores)
    fmt.Println(scores) // [20 40]
}

Menambah ke slice berbeda. append di dalam fungsi memberikan slice yang lebih panjang ke variabel milik fungsi itu, dan pemanggil tidak pernah melihatnya. Jadi fungsi yang memanjangkan slice harus mengembalikan hasilnya, dan pemanggil harus menyimpannya, persis seperti append sendiri:

func addBonusWrong(numbers []int) {
    numbers = append(numbers, 100)
}

func addBonus(numbers []int) []int {
    return append(numbers, 100)
}

func main() {
    scores := []int{20, 40}

    addBonusWrong(scores)
    fmt.Println(scores) // [20 40]

    scores = addBonus(scores)
    fmt.Println(scores) // [20 40 100]
}

Ada satu efek lagi yang perlu diketahui. Menghapus dari tengah memindahkan item di dalam penyimpanan yang dibagi, jadi variabel lain yang berbagi item itu melihat itemnya bergeser:

fruits := []string{"apple", "banana", "cherry"}
other := fruits

fruits = append(fruits[:1], fruits[2:]...)
fmt.Println(fruits) // [apple cherry]
fmt.Println(other)  // [apple cherry cherry]

Kalau kamu masih butuh versi lamanya, buat salinan lebih dulu. Contoh-contoh ini ada di exercises/22/03-sharing.go.

PR

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

Soal 1 — Game favorit

Buka exercises/22/homework/01-favourite-games.go. Buat slice berisi lima nama game. Dengan perulangan range, cetak masing-masing sebagai 1. Minecraft, 2. Terraria, dan seterusnya.

Soal 2 — Jumlahkan slice

Buka exercises/22/homework/02-sum-list.go. Slice berisi angka sudah dideklarasikan di atas. Hitung dan cetak jumlahnya.

Soal 3 — Sisip dan hapus

Buka exercises/22/homework/03-insert-remove.go. Starter ini punya slice pendek. Lakukan empat hal, dan cetak seluruh slice setelah setiap langkah:

  1. Tambahkan item baru di akhir.
  2. Sisipkan item lain di posisi 0.
  3. Hapus item terakhir.
  4. Hapus item di posisi 1.

Tantangan — Balik slice

Buka exercises/22/homework/04-reverse.go. Definisikan fungsi reverse(items []string) []string yang mengembalikan slice baru dengan item terbalik, tanpa mengubah aslinya. Uji pada slice berisi string, dan cetak sebelum dan sesudahnya.

Stuck atau sudah selesai? Buka halaman solusi PR.