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.

Referensi

  1. 1Slice types — The Go Programming Language Specification
  2. 2Slices — A Tour of Go
  3. 3Arrays, slices (and strings): The mechanics of ‘append’ — The Go Blog