BAB 34: Random Number

Pelajari cara membangkitkan angka acak di Go menggunakan package math/rand, memahami konsep seed, dan membangun simulasi yang reproducible maupun non-deterministic.

Setelah bab sebelumnya membahas bagaimana data ditampilkan — berapa desimal, dalam basis apa, dengan padding seperti apa — sekarang kita masuk ke pertanyaan yang berbeda: bagaimana program Go bisa menghasilkan data yang tidak bisa diprediksi? Angka acak adalah fondasi dari banyak hal: simulasi, pengujian dengan data dummy, pemilihan elemen secara random, shuffling, bahkan mekanisme di balik game sederhana. Go menyediakan package math/rand untuk kebutuhan ini.

Pseudo-Random: Acak yang Bisa Diprediksi

Komputer adalah mesin deterministik — ia tidak bisa menghasilkan sesuatu yang benar-benar acak. Yang dilakukan math/rand adalah menghasilkan pseudo-random number: urutan angka yang terlihat acak tapi sebenarnya dihasilkan oleh algoritma matematika dari titik awal tertentu.

Titik awal itu disebut seed. Seed yang sama selalu menghasilkan urutan angka yang identik. Ini bukan kelemahan — justru fitur. Dalam pengujian, kamu ingin hasil yang bisa direproduksi. Dalam produksi, kamu ingin hasil yang berbeda setiap kali program dijalankan.

// random.go
package main

import (
    "fmt"
    "math/rand"
)

func main() {
    // seed tetap: hasil selalu sama
    generator := rand.New(rand.NewSource(42))

    fmt.Println(generator.Intn(100))
    fmt.Println(generator.Intn(100))
    fmt.Println(generator.Intn(100))
}
63
98
14

Jalankan program ini sepuluh kali — angkanya akan selalu 63, 98, 14 dalam urutan yang sama. Seed 42 adalah titik awal yang menentukan seluruh deret yang akan dihasilkan.

Seed Dinamis untuk Hasil yang Berbeda

Untuk mendapatkan angka yang berbeda setiap eksekusi, gunakan seed yang berubah setiap kali program berjalan. Waktu sistem adalah pilihan klasik:

// random.go
package main

import (
    "fmt"
    "math/rand"
    "time"
)

func main() {
    seed := time.Now().UnixNano()
    generator := rand.New(rand.NewSource(seed))

    fmt.Println("angka acak:", generator.Intn(1000))
}

time.Now().UnixNano() mengembalikan waktu saat ini dalam nanosecond sebagai int64. Karena nilai ini berubah setiap eksekusi, seed yang dihasilkan pun berbeda, dan angka yang keluar pun tidak sama.

Sejak Go 1.20, top-level function seperti rand.Intn() (tanpa membuat instance rand.New) sudah secara otomatis menggunakan seed acak saat program dimulai. Artinya, kamu bisa langsung pakai rand.Intn(100) tanpa setup seed manual — tapi membuat instance sendiri lewat rand.New tetap disarankan jika kamu butuh kontrol penuh atas reproducibility.

Fungsi-Fungsi Pembangkit Angka

Package math/rand menyediakan berbagai jenis angka yang bisa dihasilkan. Berikut yang paling sering dipakai:

// random.go
package main

import (
    "fmt"
    "math/rand"
    "time"
)

func main() {
    g := rand.New(rand.NewSource(time.Now().UnixNano()))

    // integer dari 0 sampai n-1
    fmt.Printf("Intn(10)    : %d\n", g.Intn(10))

    // integer tak terbatas
    fmt.Printf("Int()       : %d\n", g.Int())

    // float antara 0.0 dan 1.0
    fmt.Printf("Float64()   : %.4f\n", g.Float64())

    // permutasi urutan [0, n)
    fmt.Printf("Perm(5)     : %v\n", g.Perm(5))
}
Intn(10)    : 7
Int()       : 5577006791947779410
Float64()   : 0.6046
Perm(5)     : [1 4 2 3 0]

Intn(n) adalah yang paling sering dibutuhkan: menghasilkan integer dari 0 sampai n-1. Float64() berguna untuk probabilitas atau rasio. Perm(n) menghasilkan slice berisi permutasi acak dari [0, n) — fondasi dari operasi shuffle.

Shuffle: Mengacak Urutan Slice

Mengacak urutan elemen dalam slice adalah salah satu penggunaan paling praktis dari math/rand. Package ini menyediakan method Shuffle yang bekerja dengan callback swap:

// random.go
package main

import (
    "fmt"
    "math/rand"
    "time"
)

func main() {
    g := rand.New(rand.NewSource(time.Now().UnixNano()))

    pertanyaan := []string{
        "Apa itu goroutine?",
        "Jelaskan perbedaan slice dan array.",
        "Kapan menggunakan interface?",
        "Apa fungsi defer?",
        "Bagaimana cara kerja channel?",
    }

    g.Shuffle(len(pertanyaan), func(i, j int) {
        pertanyaan[i], pertanyaan[j] = pertanyaan[j], pertanyaan[i]
    })

    fmt.Println("Urutan soal ujian:")
    for nomor, soal := range pertanyaan {
        fmt.Printf("%d. %s\n", nomor+1, soal)
    }
}
Urutan soal ujian:
1. Bagaimana cara kerja channel?
2. Apa itu goroutine?
3. Kapan menggunakan interface?
4. Jelaskan perbedaan slice dan array.
5. Apa fungsi defer?

Shuffle menerima panjang slice dan fungsi swap. Fungsi swap diberi dua indeks i dan j, dan tugasnya menukar elemen di posisi tersebut. Pola pertanyaan[i], pertanyaan[j] = pertanyaan[j], pertanyaan[i] adalah idiom swap Go yang sudah familiar sejak bab-bab sebelumnya.

Angka dalam Rentang Tertentu

Intn(n) menghasilkan angka dari 0 sampai n-1. Untuk mendapatkan angka dalam rentang [min, max], perlu sedikit aritmetika:

// random.go
package main

import (
    "fmt"
    "math/rand"
    "time"
)

func angkaDalamRentang(g *rand.Rand, min, max int) int {
    return min + g.Intn(max-min+1)
}

func main() {
    g := rand.New(rand.NewSource(time.Now().UnixNano()))

    // simulasi dadu enam sisi
    for lemparan := 1; lemparan <= 5; lemparan++ {
        hasil := angkaDalamRentang(g, 1, 6)
        fmt.Printf("lemparan %d: %d\n", lemparan, hasil)
    }
}
lemparan 1: 4
lemparan 2: 1
lemparan 3: 6
lemparan 2: 2
lemparan 5: 3

Formula min + Intn(max-min+1) adalah rumus standar untuk angka acak dalam rentang inklusif. Intn(max-min+1) menghasilkan 0 sampai max-min, lalu penambahan min menggeser rentang ke posisi yang diinginkan.

Simulasi Reproducible

Salah satu kekuatan pseudo-random adalah kemampuan untuk mereproduksi skenario yang sama. Ini sangat berguna dalam pengujian — kamu ingin memastikan fungsi bekerja benar dengan data acak yang sama setiap kali test dijalankan:

// random.go
package main

import (
    "fmt"
    "math/rand"
)

func simulasiTingkatKehadiran(g *rand.Rand, jumlahPeserta int) int {
    hadir := 0
    for i := 0; i < jumlahPeserta; i++ {
        if g.Float64() < 0.75 { // 75% kemungkinan hadir
            hadir++
        }
    }
    return hadir
}

func main() {
    seedTetap := int64(100)
    g := rand.New(rand.NewSource(seedTetap))

    hasil := simulasiTingkatKehadiran(g, 50)
    fmt.Printf("Dari 50 peserta, hadir: %d\n", hasil)
}
Dari 50 peserta, hadir: 39

Dengan seed 100, hasil 39 ini akan muncul setiap kali kode dijalankan — tidak peduli di mesin mana atau kapan dijalankan. Ini adalah reproducibility yang dibutuhkan saat menulis unit test untuk fungsi yang melibatkan randomness.

math/rand tidak cocok untuk keperluan keamanan seperti token otentikasi, session ID, atau kunci kriptografi. Untuk itu, gunakan crypto/rand yang menggunakan sumber entropi sistem operasi.

Latihan

Latihan 1 — Daftar tugas acak: Buat slice berisi 8 nama tugas bertipe string. Gunakan Shuffle untuk mengacak urutannya, lalu cetak 3 tugas pertama sebagai “tugas prioritas hari ini”. Jalankan beberapa kali dan pastikan hasilnya berbeda setiap kali.

Latihan 2 — Simulasi koin: Buat fungsi lemparKoin(g *rand.Rand) string yang mengembalikan "heads" atau "tails". Panggil fungsi itu 1000 kali dan hitung berapa kali masing-masing sisi muncul. Cetak hasilnya sebagai persentase — seharusnya mendekati 50/50.

Latihan 3 — Reproducible test: Tulis fungsi pilihPemenang(g *rand.Rand, peserta []string) string yang memilih satu nama secara acak dari slice. Pastikan dengan seed yang sama, pemenang yang terpilih selalu sama — dan dengan seed berbeda, hasilnya bervariasi. Demonstrasikan keduanya dalam satu main.

Kemampuan membangkitkan angka acak melengkapi toolkit standar yang sudah kamu kumpulkan. Satu hal yang menarik: di bab ini kita menggunakan time.Now().UnixNano() hanya untuk mengambil satu angka integer sebagai seed. Package time di Go sebenarnya jauh lebih kaya — ia menyediakan cara untuk membuat, memformat, mem-parsing, dan membandingkan nilai tanggal dan waktu secara lengkap. Itu yang akan kita jelajahi di bab berikutnya.

Referensi

  1. 1Package math/rand — Go Standard Library Documentation
  2. 2Package crypto/rand — Go Standard Library Documentation
  3. 3Random Numbers — Go by Example