BAB 13: Slice — Array yang Bisa Tumbuh
Pelajari slice di Go: struktur data fleksibel yang dibangun di atas array. Pahami cara deklarasi, append, delete, copy, dan cara slice berbagi memori dengan array asalnya.
Di bab sebelumnya, kamu menemukan batasan utama array: ukurannya harus ditentukan saat deklarasi dan tidak bisa berubah. Itu masalah nyata ketika kamu tidak tahu berapa banyak data yang akan datang — misalnya membaca baris dari file, menerima item dari API, atau mengumpulkan input user. Go menyelesaikan ini dengan slice.
Slice bukan struktur data yang sepenuhnya terpisah dari array. Slice adalah jendela yang menunjuk ke array di bawahnya — punya panjang yang bisa berubah dan kapasitas yang bisa diperluas secara otomatis. Memahami hubungan ini adalah kunci agar kamu tidak terkejut dengan perilaku slice nantinya.
Deklarasi dan Inisialisasi
Ada tiga cara umum membuat slice di Go, masing-masing untuk situasi yang berbeda.
// main.go
package main
import "fmt"
func main() {
// 1. deklarasi kosong — nilainya nil, belum punya backing array
var nama []string
fmt.Println("nil slice:", nama, "| panjang:", len(nama))
// 2. literal — langsung dengan nilai awal
buah := []string{"apel", "jeruk", "mangga"}
fmt.Println("literal:", buah)
// 3. make — buat slice dengan panjang dan kapasitas tertentu
angka := make([]int, 3, 5) // panjang 3, kapasitas 5
fmt.Println("make:", angka, "| panjang:", len(angka), "| kapasitas:", cap(angka))
}
nil slice: [] | panjang: 0
literal: [apel jeruk mangga]
make: [0 0 0] | panjang: 3 | kapasitas: 5
make berguna ketika kamu sudah tahu kira-kira berapa banyak elemen yang akan ditampung — memberikan kapasitas awal yang lebih besar mencegah realokasi memori yang terlalu sering.
Slice dari Array
Karena slice adalah jendela ke array, kamu bisa membuat slice yang menunjuk ke bagian tertentu dari sebuah array.
// main.go
package main
import "fmt"
func main() {
nilai := [7]int{10, 20, 30, 40, 50, 60, 70}
// sintaks [start:end] — dari indeks start sampai sebelum end
tengah := nilai[2:5]
fmt.Println("array asli:", nilai)
fmt.Println("slice tengah:", tengah)
// modifikasi lewat slice memengaruhi array asli
tengah[0] = 999
fmt.Println("setelah modifikasi slice:", nilai)
}
array asli: [10 20 30 40 50 60 70]
slice tengah: [30 40 50]
setelah modifikasi slice: [10 20 999 40 50 60 70]
Berbeda dari array, perubahan pada slice akan mengubah array asal karena keduanya berbagi memori yang sama. Ini adalah perilaku yang sering mengejutkan pemula — pastikan kamu sadar apakah slice yang kamu operasikan “memiliki” datanya sendiri atau berbagi dengan array lain.
Menambah Elemen dengan append
Fungsi append menambahkan satu atau lebih elemen ke akhir slice dan mengembalikan slice yang baru.
// main.go
package main
import "fmt"
func main() {
buah := []string{"apel", "jeruk"}
fmt.Println("awal:", buah, "| cap:", cap(buah))
buah = append(buah, "mangga")
fmt.Println("+ mangga:", buah, "| cap:", cap(buah))
buah = append(buah, "pisang", "durian")
fmt.Println("+ 2 lagi:", buah, "| cap:", cap(buah))
// menggabungkan dua slice dengan ...
tambahan := []string{"nanas", "semangka"}
buah = append(buah, tambahan...)
fmt.Println("gabung:", buah)
}
awal: [apel jeruk] | cap: 2
+ mangga: [apel jeruk mangga] | cap: 4
+ 2 lagi: [apel jeruk mangga pisang durian] | cap: 8
gabung: [apel jeruk mangga pisang durian nanas semangka]
Perhatikan kapasitas yang berlipat ganda ketika habis. Ini adalah mekanisme Go untuk meminimalkan alokasi memori — saat kapasitas penuh, Go mengalokasikan backing array baru dengan kapasitas dua kali lipat.
Menghapus Elemen
Go tidak punya fungsi khusus untuk menghapus elemen. Teknik standarnya adalah menggabungkan dua bagian slice menggunakan append.
// main.go
package main
import "fmt"
func main() {
buah := []string{"apel", "jeruk", "mangga", "pisang", "durian"}
fmt.Println("sebelum:", buah)
// hapus elemen di indeks 2 ("mangga")
hapusIndeks := 2
buah = append(buah[:hapusIndeks], buah[hapusIndeks+1:]...)
fmt.Println("sesudah:", buah)
}
sebelum: [apel jeruk mangga pisang durian]
sesudah: [apel jeruk pisang durian]
Sintaks buah[:2] mengambil elemen sebelum indeks yang dihapus, lalu buah[3:]... mengambil elemen sesudahnya. Operator ... “membuka” slice agar bisa diterima sebagai argumen individual oleh append.
Menyalin Slice dengan copy
Karena dua slice bisa berbagi backing array yang sama, ada kalanya kamu butuh salinan yang benar-benar independen. Fungsi copy menangani ini.
// main.go
package main
import "fmt"
func main() {
sumber := []int{1, 2, 3, 4, 5}
tujuan := make([]int, len(sumber))
n := copy(tujuan, sumber)
fmt.Println("disalin sebanyak:", n, "elemen")
fmt.Println("sumber :", sumber)
fmt.Println("tujuan :", tujuan)
// modifikasi tujuan tidak memengaruhi sumber
tujuan[0] = 999
fmt.Println("setelah modifikasi tujuan:", sumber)
}
disalin sebanyak: 5 elemen
sumber : [1 2 3 4 5]
tujuan : [1 2 3 4 5]
setelah modifikasi tujuan: [1 2 3 4 5]
Iterasi dengan for range
Seperti array, slice bisa diiterasi dengan for range. Kamu sudah mengenal pola ini dari Bab 11 dan 12.
// main.go
package main
import "fmt"
func main() {
nilai := []int{85, 92, 78, 95, 88}
total := 0
for i, n := range nilai {
fmt.Printf(" nilai[%d] = %d\n", i, n)
total += n
}
rataRata := float64(total) / float64(len(nilai))
fmt.Printf("Rata-rata: %.1f\n", rataRata)
}
nilai[0] = 85
nilai[1] = 92
nilai[2] = 78
nilai[3] = 95
nilai[4] = 88
Rata-rata: 87.6
Jika hanya butuh nilai tanpa indeks, gunakan _ untuk mengabaikan indeks: for _, n := range nilai.
Slice Multidimensi
Slice bisa bersarang untuk merepresentasikan data dua dimensi — berbeda dari array multidimensi, setiap baris bisa punya panjang yang berbeda.
// main.go
package main
import "fmt"
func main() {
// nilai ujian per siswa — tiap siswa bisa punya jumlah mata pelajaran berbeda
nilaiSiswa := [][]int{
{80, 85, 90}, // siswa 0: 3 mata pelajaran
{75, 88}, // siswa 1: 2 mata pelajaran
{92, 79, 85, 91}, // siswa 2: 4 mata pelajaran
}
for i, siswa := range nilaiSiswa {
total := 0
for _, n := range siswa {
total += n
}
avg := float64(total) / float64(len(siswa))
fmt.Printf("Siswa %d: %v | rata-rata: %.1f\n", i, siswa, avg)
}
}
Siswa 0: [80 85 90] | rata-rata: 85.0
Siswa 1: [75 88] | rata-rata: 81.5
Siswa 2: [92 79 85 91] | rata-rata: 86.8
Fleksibilitas inilah yang membuat slice multidimensi lebih sering dipakai daripada array multidimensi di kode Go nyata.
Latihan
Latihan 1 — Filter nilai lulus:
Dari slice []int{72, 45, 88, 55, 91, 63, 78, 49}, buat slice baru yang hanya berisi nilai di atas 60. Gunakan append untuk mengumpulkan nilai yang lolos, dan tampilkan hasilnya beserta jumlah siswa yang lulus.
Latihan 2 — Hapus duplikat:
Dari slice []string{"apel", "jeruk", "apel", "mangga", "jeruk", "pisang"}, buat slice baru tanpa elemen duplikat. Gunakan teknik pengecekan keberadaan elemen sebelum menambahkan ke slice hasil.
Latihan 3 — Rotasi slice:
Tulis program yang merotasi slice []int{1, 2, 3, 4, 5} satu posisi ke kiri sehingga hasilnya menjadi [2 3 4 5 1]. Lakukan tanpa membuat slice baru — manipulasi slice yang sudah ada menggunakan append.
Dengan slice, kamu punya alat untuk mengelola koleksi data dengan ukuran yang tidak diketahui sebelumnya. Namun ada satu kasus yang belum bisa diselesaikan slice: bagaimana jika kamu ingin mengakses data berdasarkan nama atau label, bukan posisi? Untuk itu, Go menyediakan map — dan itulah yang akan kita bahas di bab berikutnya.