Sort #
Sorting adalah operasi fundamental yang muncul di mana-mana — menampilkan daftar produk berdasarkan harga, mengurutkan log berdasarkan waktu, mencari data yang sudah terurut, atau menentukan elemen terbesar dan terkecil. Package sort di Go menyediakan semua yang diperlukan: sorting untuk tipe primitif, sorting slice dengan comparator kustom, sorting yang stabil untuk menjaga urutan elemen yang sama, dan pencarian biner untuk mencari nilai di data yang sudah terurut. Go 1.21 juga memperkenalkan package slices yang menyediakan fungsi sorting generik yang lebih modern dan ergonomis. Memahami keduanya memberi kamu alat yang tepat untuk setiap skenario sorting.
Gambaran Besar #
flowchart TD
S["Sorting di Go"] --> Sort["package sort\n(pre-Go 1.21)"]
S --> Slices["package slices\n(Go 1.21+, generik)"]
Sort --> SP["sort.Ints\nsort.Strings\nsort.Float64s\n(tipe primitif)"]
Sort --> SS["sort.Slice\nsort.SliceStable\n(slice dengan comparator)"]
Sort --> SI["sort.Sort\nsort.Stable\n(interface Len/Less/Swap)"]
Sort --> SB["sort.Search\n(pencarian biner)"]
Sort --> SC["sort.IsSorted\nsort.Reverse\n(utilitas)"]
Slices --> SL1["slices.Sort\nslices.SortFunc\n(generik, lebih cepat)"]
Slices --> SL2["slices.SortStableFunc\nslices.IsSorted\nslices.IsSortedFunc"]
Slices --> SL3["slices.BinarySearch\nslices.BinarySearchFunc"]
Slices --> SL4["slices.Min / slices.Max\nslices.MinFunc / slices.MaxFunc"]
style S fill:#4f86c6,color:#fff
style Sort fill:#e8f5e9
style Slices fill:#e3f2fdSorting Tipe Primitif #
Untuk slice dari tipe primitif, sort menyediakan fungsi langsung tanpa perlu mendefinisikan comparator:
package main
import (
"fmt"
"sort"
)
func main() {
// Integer
angka := []int{5, 2, 8, 1, 9, 3, 7, 4, 6}
sort.Ints(angka)
fmt.Println(angka) // [1 2 3 4 5 6 7 8 9]
// String — urutan leksikografis (berdasarkan nilai byte)
kata := []string{"pisang", "apel", "mangga", "jeruk", "durian"}
sort.Strings(kata)
fmt.Println(kata) // [apel durian jeruk mangga pisang]
// Float64
nilai := []float64{3.14, 1.41, 2.71, 1.73, 2.23}
sort.Float64s(nilai)
fmt.Println(nilai) // [1.41 1.73 2.23 2.71 3.14]
// Periksa apakah sudah terurut
fmt.Println(sort.IntsAreSorted(angka)) // true
fmt.Println(sort.StringsAreSorted(kata)) // true
fmt.Println(sort.Float64sAreSorted(nilai)) // true
// Urutan terbalik — bungkus dengan sort.Reverse
sort.Sort(sort.Reverse(sort.IntSlice(angka)))
fmt.Println(angka) // [9 8 7 6 5 4 3 2 1]
}
Dengan Package slices (Go 1.21+) #
import (
"cmp"
"slices"
)
// slices.Sort — lebih cepat dan generik
angka := []int{5, 2, 8, 1, 9, 3}
slices.Sort(angka)
fmt.Println(angka) // [1 2 3 5 8 9]
kata := []string{"pisang", "apel", "mangga"}
slices.Sort(kata)
fmt.Println(kata) // [apel mangga pisang]
// Urutan terbalik dengan cmp.Reverse
slices.SortFunc(angka, func(a, b int) int {
return cmp.Compare(b, a) // b sebelum a = descending
})
fmt.Println(angka) // [9 8 5 3 2 1]
// Cari nilai minimum dan maksimum
min := slices.Min(angka) // 1
max := slices.Max(angka) // 9
fmt.Println(min, max)
sort.Slice — Sorting dengan Comparator Kustom #
sort.Slice adalah fungsi yang paling sering dipakai untuk sorting struct atau data kompleks — kamu cukup mendefinisikan fungsi less(i, j int) bool yang menentukan apakah elemen ke-i harus berada sebelum elemen ke-j.
type Produk struct {
ID int
Nama string
Harga float64
Kategori string
Stok int
}
produk := []Produk{
{1, "Laptop", 15000000, "elektronik", 10},
{2, "Buku Go", 150000, "buku", 50},
{3, "Mouse", 250000, "elektronik", 30},
{4, "Keyboard", 500000, "elektronik", 20},
{5, "Novel", 80000, "buku", 100},
}
// Sort berdasarkan harga (ascending)
sort.Slice(produk, func(i, j int) bool {
return produk[i].Harga < produk[j].Harga
})
for _, p := range produk {
fmt.Printf("%-10s Rp%,.0f\n", p.Nama, p.Harga)
}
// Novel Rp80.000
// Buku Go Rp150.000
// Mouse Rp250.000
// Keyboard Rp500.000
// Laptop Rp15.000.000
// Sort berdasarkan harga (descending)
sort.Slice(produk, func(i, j int) bool {
return produk[i].Harga > produk[j].Harga
})
// Sort berdasarkan nama (alphabetical)
sort.Slice(produk, func(i, j int) bool {
return produk[i].Nama < produk[j].Nama
})
// Sort berdasarkan kategori, lalu harga dalam kategori
sort.Slice(produk, func(i, j int) bool {
if produk[i].Kategori != produk[j].Kategori {
return produk[i].Kategori < produk[j].Kategori
}
return produk[i].Harga < produk[j].Harga
})
sort.SliceStable — Menjaga Urutan Relatif #
sort.SliceStable menjamin elemen yang dianggap sama (fungsi less mengembalikan false untuk keduanya) tetap dalam urutan relatif yang sama seperti sebelum sorting:
flowchart LR
subgraph Input["Input (urutan asli)"]
A["A=1"] --> B["B=1"] --> C["C=2"] --> D["D=1"] --> E["E=2"]
end
subgraph Unstable["sort.Slice — tidak stabil"]
U1["B=1, D=1, A=1, C=2, E=2\natau urutan lain yang valid"]
end
subgraph Stable["sort.SliceStable — stabil"]
S1["A=1, B=1, D=1, C=2, E=2\nurutan relatif A→B→D terjaga"]
end
Input --> Unstable
Input --> Stable
style Unstable fill:#fff3e0
style Stable fill:#e8f5e9type Pesanan struct {
ID int
Pelanggan string
Prioritas int
Waktu time.Time
}
pesanan := []Pesanan{
{1, "Budi", 2, time.Now().Add(-5 * time.Minute)},
{2, "Ani", 1, time.Now().Add(-3 * time.Minute)},
{3, "Candra", 2, time.Now().Add(-4 * time.Minute)},
{4, "Dewi", 1, time.Now().Add(-2 * time.Minute)},
{5, "Eko", 2, time.Now().Add(-1 * time.Minute)},
}
// ANTI-PATTERN: sort.Slice bisa mengacak pesanan dengan prioritas sama
// Pesanan prioritas 2: {1,3,5} bisa menjadi {3,1,5} atau {5,1,3} dll
sort.Slice(pesanan, func(i, j int) bool {
return pesanan[i].Prioritas < pesanan[j].Prioritas
})
// BENAR: SliceStable menjaga urutan waktu dalam prioritas yang sama
// Pesanan prioritas 2 tetap dalam urutan: {1,3,5} (sesuai waktu masuk)
sort.SliceStable(pesanan, func(i, j int) bool {
return pesanan[i].Prioritas < pesanan[j].Prioritas
})
// Output: Ani(1), Dewi(1), Budi(2), Candra(2), Eko(2)
// Dalam prioritas yang sama, urutan waktu terjaga
sort.Sort — Interface Lengkap #
Untuk kontrol maksimal atau tipe yang sering disorting, implementasikan interface sort.Interface:
type Interface interface {
Len() int
Less(i, j int) bool
Swap(i, j int)
}
// Implementasi untuk slice Produk — bisa digunakan berulang
type ProdukSlice []Produk
func (p ProdukSlice) Len() int { return len(p) }
func (p ProdukSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
func (p ProdukSlice) Less(i, j int) bool { return p[i].Harga < p[j].Harga }
// Gunakan
daftar := ProdukSlice(produk)
sort.Sort(daftar)
sort.Stable(daftar) // versi stabil
sort.IsSorted(daftar) // periksa apakah sudah terurut
// Reverse tanpa mendefinisikan tipe baru
sort.Sort(sort.Reverse(daftar))
Multi-Key Sort dengan Interface #
// Sorting dengan banyak kriteria menggunakan pendekatan "less" yang bisa dikomposis
type MultiSorter struct {
items []Produk
less []func(a, b Produk) bool
}
func (ms *MultiSorter) Sort(items []Produk) {
ms.items = items
sort.Sort(ms)
}
func (ms *MultiSorter) Len() int { return len(ms.items) }
func (ms *MultiSorter) Swap(i, j int) { ms.items[i], ms.items[j] = ms.items[j], ms.items[i] }
func (ms *MultiSorter) Less(i, j int) bool {
a, b := ms.items[i], ms.items[j]
var k int
for k = 0; k < len(ms.less)-1; k++ {
less := ms.less[k]
switch {
case less(a, b):
return true
case less(b, a):
return false
}
// sama — coba kriteria berikutnya
}
return ms.less[k](a, b)
}
// Penggunaan yang ekspresif
sorter := &MultiSorter{
less: []func(a, b Produk) bool{
func(a, b Produk) bool { return a.Kategori < b.Kategori }, // kategori dulu
func(a, b Produk) bool { return a.Harga < b.Harga }, // lalu harga
func(a, b Produk) bool { return a.Nama < b.Nama }, // lalu nama
},
}
sorter.Sort(produk)
sort.Search — Pencarian Biner #
sort.Search melakukan pencarian biner pada slice yang sudah terurut. Ia mencari indeks terkecil i di [0, n) di mana f(i) mengembalikan true:
flowchart TD
A["sort.Search(n, f)"] --> B["Cari indeks terkecil i\ndi mana f(i) = true"]
B --> C{"Data harus\nsudah terurut!"}
C --> D["Binary search:\nO(log n)"]
D --> E["Kembalikan i\n(n jika tidak ada)"]
subgraph Contoh["Contoh: cari 7 di [1,3,5,7,9,11]"]
F["n=6, cari indeks i\ndi mana arr[i] >= 7"]
G["i=3 → arr[3]=7 ✓"]
end
style D fill:#e8f5e9
style E fill:#e3f2fd// Data harus SUDAH TERURUT sebelum pencarian biner
angka := []int{1, 3, 5, 7, 9, 11, 13, 15, 17, 19}
// Cari angka 7
target := 7
i := sort.SearchInts(angka, target)
if i < len(angka) && angka[i] == target {
fmt.Printf("Ditemukan %d di indeks %d\n", target, i)
} else {
fmt.Printf("%d tidak ditemukan\n", target)
}
// Ditemukan 7 di indeks 3
// Cari angka yang tidak ada
i = sort.SearchInts(angka, 8)
fmt.Printf("8 tidak ada, posisi insertnya: %d\n", i)
// 8 tidak ada, posisi insertnya: 4
// sort.Search generik — untuk tipe apapun
kata := []string{"apel", "durian", "jeruk", "mangga", "pisang"}
cari := "jeruk"
j := sort.SearchStrings(kata, cari)
if j < len(kata) && kata[j] == cari {
fmt.Printf("Ditemukan %q di indeks %d\n", cari, j)
}
// Ditemukan "jeruk" di indeks 2
// sort.Search untuk kondisi yang lebih kompleks
harga := []float64{50000, 100000, 150000, 200000, 500000, 1000000}
// Cari produk dengan harga >= 200000 (indeks pertama yang memenuhi)
batas := 200000.0
k := sort.Search(len(harga), func(i int) bool {
return harga[i] >= batas
})
fmt.Printf("Harga >= %.0f mulai dari indeks %d: %.0f\n",
batas, k, harga[k])
// Harga >= 200000 mulai dari indeks 3: 200000
Pencarian Biner dengan slices (Go 1.21+) #
import "slices"
angka := []int{1, 3, 5, 7, 9, 11}
// BinarySearch — lebih ergonomis dari sort.Search
i, ditemukan := slices.BinarySearch(angka, 7)
fmt.Println(i, ditemukan) // 3 true
i, ditemukan = slices.BinarySearch(angka, 8)
fmt.Println(i, ditemukan) // 4 false — posisi insert jika ingin dimasukkan
// BinarySearchFunc — untuk struct
produk := []Produk{
{1, "Buku", 80000, "buku", 100},
{2, "Mouse", 250000, "elektronik", 30},
{3, "Laptop", 15000000, "elektronik", 10},
}
// Sudah terurut berdasarkan harga
slices.SortFunc(produk, func(a, b Produk) int {
return cmp.Compare(a.Harga, b.Harga)
})
// Cari produk dengan harga 250000
idx, found := slices.BinarySearchFunc(produk, 250000.0, func(p Produk, harga float64) int {
return cmp.Compare(p.Harga, harga)
})
fmt.Println(idx, found) // 1 true
Sorting dengan slices.SortFunc (Go 1.21+) #
Package slices memperkenalkan sorting generik yang lebih ekspresif dan lebih cepat dari sort.Slice:
import (
"cmp"
"slices"
)
type Karyawan struct {
Nama string
Departemen string
Gaji float64
Bergabung time.Time
}
karyawan := []Karyawan{
{"Budi", "Engineering", 15000000, time.Date(2021, 3, 1, 0, 0, 0, 0, time.Local)},
{"Ani", "Marketing", 12000000, time.Date(2020, 6, 15, 0, 0, 0, 0, time.Local)},
{"Candra", "Engineering", 18000000, time.Date(2019, 1, 10, 0, 0, 0, 0, time.Local)},
{"Dewi", "HR", 10000000, time.Date(2022, 9, 5, 0, 0, 0, 0, time.Local)},
{"Eko", "Engineering", 15000000, time.Date(2020, 3, 1, 0, 0, 0, 0, time.Local)},
}
// Sort berdasarkan gaji descending
slices.SortFunc(karyawan, func(a, b Karyawan) int {
return cmp.Compare(b.Gaji, a.Gaji) // b,a = descending
})
// Sort multi-kriteria: departemen ascending, lalu gaji descending
slices.SortFunc(karyawan, func(a, b Karyawan) int {
if n := cmp.Compare(a.Departemen, b.Departemen); n != 0 {
return n
}
return cmp.Compare(b.Gaji, a.Gaji)
})
// Sort stabil — menjaga urutan elemen yang sama
slices.SortStableFunc(karyawan, func(a, b Karyawan) int {
return cmp.Compare(a.Departemen, b.Departemen)
})
// Dalam departemen yang sama, urutan asli terjaga
// Min dan Max
termuda := slices.MinFunc(karyawan, func(a, b Karyawan) int {
return a.Bergabung.Compare(b.Bergabung) // Compare untuk time.Time
})
fmt.Printf("Paling baru bergabung: %s (%s)\n",
termuda.Nama, termuda.Bergabung.Format("2006-01-02"))
sort.Reverse — Membalik Urutan #
// Membalik slice tipe primitif
angka := []int{3, 1, 4, 1, 5, 9, 2, 6}
sort.Sort(sort.Reverse(sort.IntSlice(angka)))
fmt.Println(angka) // [9 6 5 4 3 2 1 1]
// Membalik slice string
kata := []string{"apel", "mangga", "jeruk"}
sort.Sort(sort.Reverse(sort.StringSlice(kata)))
fmt.Println(kata) // [mangga jeruk apel]
// Membalik slice struct
sort.Slice(produk, func(i, j int) bool {
// Descending: kembalikan true jika i LEBIH BESAR dari j
return produk[i].Harga > produk[j].Harga
})
// Atau dengan slices (lebih bersih):
slices.SortFunc(produk, func(a, b Produk) int {
return cmp.Compare(b.Harga, a.Harga) // b sebelum a = descending
})
Pola Penggunaan di Produksi #
Sorting Hasil Query dengan Kriteria Dinamis #
type UrutanField string
type Arah string
const (
ArahAsc Arah = "asc"
ArahDesc Arah = "desc"
)
type KriteriaUrutan struct {
Field UrutanField
Arah Arah
}
func urutkanProduk(produk []Produk, kriteria []KriteriaUrutan) {
sort.SliceStable(produk, func(i, j int) bool {
a, b := produk[i], produk[j]
for _, k := range kriteria {
var lebihKecil bool
var sama bool
switch k.Field {
case "nama":
lebihKecil = a.Nama < b.Nama
sama = a.Nama == b.Nama
case "harga":
lebihKecil = a.Harga < b.Harga
sama = a.Harga == b.Harga
case "stok":
lebihKecil = a.Stok < b.Stok
sama = a.Stok == b.Stok
case "kategori":
lebihKecil = a.Kategori < b.Kategori
sama = a.Kategori == b.Kategori
default:
continue
}
if sama {
continue // coba kriteria berikutnya
}
if k.Arah == ArahDesc {
return !lebihKecil
}
return lebihKecil
}
return false // semua kriteria sama
})
}
// Penggunaan: sort berdasarkan kategori asc, lalu harga desc
urutkanProduk(produk, []KriteriaUrutan{
{Field: "kategori", Arah: ArahAsc},
{Field: "harga", Arah: ArahDesc},
})
Top-N Elemen tanpa Sort Penuh #
Untuk mengambil N elemen terbesar/terkecil dari slice besar, sorting penuh adalah pemborosan — O(n log n) saat kita hanya butuh O(n):
// Ambil 5 produk termahal tanpa sort penuh
func topNTermahal(produk []Produk, n int) []Produk {
if n >= len(produk) {
// Sort penuh jika n >= panjang slice
hasil := make([]Produk, len(produk))
copy(hasil, produk)
sort.Slice(hasil, func(i, j int) bool {
return hasil[i].Harga > hasil[j].Harga
})
return hasil
}
// Partial sort: hanya urutkan n elemen pertama
hasil := make([]Produk, len(produk))
copy(hasil, produk)
// Gunakan selection sort parsial — O(n*k) lebih baik dari O(n log n)
// untuk k yang kecil
for i := 0; i < n; i++ {
maxIdx := i
for j := i + 1; j < len(hasil); j++ {
if hasil[j].Harga > hasil[maxIdx].Harga {
maxIdx = j
}
}
hasil[i], hasil[maxIdx] = hasil[maxIdx], hasil[i]
}
return hasil[:n]
}
Mencari Rentang dengan Pencarian Biner #
// Cari semua produk dalam rentang harga menggunakan pencarian biner
func cariRentangHarga(produk []Produk, min, max float64) []Produk {
// Pastikan sudah terurut berdasarkan harga
if !sort.SliceIsSorted(produk, func(i, j int) bool {
return produk[i].Harga < produk[j].Harga
}) {
sort.Slice(produk, func(i, j int) bool {
return produk[i].Harga < produk[j].Harga
})
}
// Cari indeks awal (harga >= min)
awal := sort.Search(len(produk), func(i int) bool {
return produk[i].Harga >= min
})
// Cari indeks akhir (harga > max)
akhir := sort.Search(len(produk), func(i int) bool {
return produk[i].Harga > max
})
return produk[awal:akhir]
}
// Penggunaan
dalamRentang := cariRentangHarga(produk, 100000, 500000)
fmt.Printf("Produk antara Rp100.000-Rp500.000: %d item\n", len(dalamRentang))
Menghapus Duplikat dari Slice Terurut #
// Setelah sort, duplikat berdekatan — mudah dihapus
func hapusDuplikat(s []string) []string {
if len(s) == 0 {
return s
}
sort.Strings(s)
j := 0
for i := 1; i < len(s); i++ {
if s[i] != s[j] {
j++
s[j] = s[i]
}
}
return s[:j+1]
}
// Versi generik dengan slices (Go 1.21+)
import "slices"
func hapusDuplikatGenerik[T comparable](s []T) []T {
slices.Sort(s)
return slices.Compact(s) // Compact menghapus duplikat berurutan
}
// Penggunaan
tags := []string{"go", "backend", "go", "api", "backend", "tutorial"}
unik := hapusDuplikat(tags)
fmt.Println(unik) // [api backend go tutorial]
Menyisipkan ke Slice Terurut #
// Sisipkan nilai ke posisi yang tepat untuk menjaga urutan
func sisipkanTerurut(s []int, val int) []int {
// Cari posisi yang tepat
i := sort.SearchInts(s, val)
// Sisipkan di posisi i
s = append(s, 0) // tambah ruang
copy(s[i+1:], s[i:]) // geser elemen
s[i] = val // sisipkan
return s
}
// Dengan slices (Go 1.21+)
func sisipkanTerurutGenerik[T cmp.Ordered](s []T, val T) []T {
i, _ := slices.BinarySearch(s, val)
return slices.Insert(s, i, val)
}
// Penggunaan
terurut := []int{1, 3, 5, 7, 9}
terurut = sisipkanTerurut(terurut, 4)
fmt.Println(terurut) // [1 3 4 5 7 9]
Performa: sort vs slices #
flowchart LR
subgraph Perbandingan["sort.Slice vs slices.SortFunc"]
P1["sort.Slice\n- Menggunakan interface{}\n (boxing/unboxing)\n- Overhead refleksi\n- Tersedia sejak Go 1\n- Lebih kompatibel"]
P2["slices.SortFunc\n- Generik, tidak ada boxing\n- Lebih cepat ~20-30%\n- Go 1.21+ saja\n- API lebih bersih"]
end
subgraph Pilih["Pilih yang mana?"]
Q{"Go version?"} --> New["Go 1.21+\n→ slices.SortFunc"]
Q --> Old["Go < 1.21\n→ sort.Slice"]
New --> Consistent["Konsisten gunakan\nslices package"]
end
style P2 fill:#e8f5e9
style P1 fill:#e3f2fd// Benchmark perbandingan (ilustrasi):
// sort.Slice: ~850 ns/op untuk 100 elemen
// slices.SortFunc: ~650 ns/op untuk 100 elemen
// (~25% lebih cepat karena generik menghindari interface boxing)
// Untuk aplikasi umum, perbedaan ini tidak signifikan
// Pilih berdasarkan Go version dan konsistensi kode
Kapan Beralih ke Alternatif #
Tetap gunakan sort / slices jika:
✓ Sorting slice dari semua tipe
✓ Pencarian biner pada data terurut
✓ Operasi sort satu kali atau jarang
Pertimbangkan struktur data lain jika:
✗ Perlu data selalu terurut saat insert/delete
→ heap (container/heap) untuk priority queue
→ tree-based structure (tidak ada di stdlib, pakai btree)
✗ Pencarian yang sangat sering pada data yang terus berubah
→ pertimbangkan sorted set atau database dengan index
Pertimbangkan algoritma kustom jika:
✗ Data sudah hampir terurut → insertion sort O(n) untuk near-sorted
✗ Hanya perlu N terkecil/terbesar → heap atau quickselect O(n)
✗ Sorting integer dalam rentang kecil → counting sort O(n+k)
✗ Sorting string banyak dengan prefix sama → radix sort
Ringkasan #
sort.Sliceuntuk sort cepat,sort.SliceStablejika urutan relatif elemen yang sama harus terjaga — misalnya sort berdasarkan prioritas saat data sudah terurut berdasarkan waktu.slices.SortFunc(Go 1.21+) adalah versi yang lebih cepat dan generik darisort.Slice— gunakan ini untuk project baru dengan Go 1.21+.- Multi-key sort: bandingkan field pertama dulu, jika sama bandingkan field kedua, dst — pola ini berlaku di
sort.Slice,sort.Sort, maupunslices.SortFunc.sort.Searchdanslices.BinarySearchhanya bekerja pada data yang sudah terurut — selalu pastikan data terurut sebelum pencarian biner, hasilnya tidak bisa dipercaya jika tidak.sort.SearchInts/sort.SearchStringsadalah shortcut untuk pencarian biner pada tipe primitif — versi generikslices.BinarySearchlebih ergonomis di Go 1.21+.- Sort stabil penting saat data sudah punya urutan bermakna (waktu, ID) dan kamu hanya menambah kriteria sort baru di atasnya — gunakan
SliceStableatauSortStableFunc.- Hapus duplikat: sort dulu, lalu iterasi untuk hapus elemen berurutan yang sama — atau gunakan
slices.Compactdi Go 1.21+.- Untuk top-N: jika N kecil dibanding panjang slice, partial selection sort
O(n*k)lebih efisien dari full sortO(n log n).