package main

import (
	"fmt"
	"runtime"
	"strings"
)

// hitungKataNaif menghitung frekuensi kata dengan potong-memotong string
// berulang: sekilas terlihat wajar, tetapi tiap strings.Fields mengalokasi
// slice baru.
func hitungKataNaif(teks string) map[string]int {
	hasil := map[string]int{}
	for _, baris := range strings.Split(teks, "\n") {
		for _, kata := range strings.Fields(baris) {
			hasil[kata]++
		}
	}
	return hasil
}

// hitungKataEfisien memakai satu kali strings.Fields untuk seluruh teks
// dan menerima kapasitas awal dari pemanggil.
func hitungKataEfisien(teks string, kapasitas int) map[string]int {
	hasil := make(map[string]int, kapasitas)
	for _, kata := range strings.Fields(teks) {
		hasil[kata]++
	}
	return hasil
}

// ukurAlokasi mengembalikan jumlah alokasi dan total byte yang terjadi
// selama menjalankan fungsi f. Angka ini deterministik untuk input tetap.
func ukurAlokasi(f func()) (alokasi int64, jumlahByte int64) {
	var awal, akhir runtime.MemStats
	runtime.ReadMemStats(&awal)
	f()
	runtime.ReadMemStats(&akhir)
	return int64(akhir.Mallocs - awal.Mallocs), int64(akhir.TotalAlloc - awal.TotalAlloc)
}

func main() {
	kalimat := strings.Repeat("belajar go itu menyenangkan dan cepat\n", 20)

	// Dua implementasi memberikan hasil yang sama...
	naif := hitungKataNaif(kalimat)
	efisien := hitungKataEfisien(kalimat, 8)
	fmt.Println("Hasil sama        :", len(naif) == len(efisien))

	// ...tetapi biaya alokasinya sangat berbeda. Tebakan saja tidak akan
	// menunjukkan ini; pengukuran yang menunjukkan.
	aN, bN := ukurAlokasi(func() { hitungKataNaif(kalimat) })
	aE, bE := ukurAlokasi(func() { hitungKataEfisien(kalimat, 8) })
	fmt.Printf("Naif      : %d alokasi, %d byte\n", aN, bN)
	fmt.Printf("Efisien   : %d alokasi, %d byte\n", aE, bE)

	// Keputusan optimisasi diambil dari data, bukan dari kesan sekilas.
	fmt.Println("Optimasi dibenarkan oleh data:", aE < aN)
}
