diff --git a/searching/interpolation.go b/searching/interpolation.go new file mode 100644 index 0000000..99c4e13 --- /dev/null +++ b/searching/interpolation.go @@ -0,0 +1,68 @@ +package searching + +// fungsi melakukan pencarian menggunakan algoritam interpolasi searching +// interpolasi searching adalah versi lanjuta dari binary searching yang menentukan +// posisi elemen dengan memperkirakan lokasinya berdasarkan nilai target +// +// Parameter: +// - sortedData []int: array tipe data int yang sudah terurut ascending +// - tebakan int: nilai yang akan dicari dalam sebuah array +// +// Return: +// - indeks pertama dari nilai yang cocok +// - error jika tidak ditemukan atau kondisi input tidak valid +func pencarianInterpolasi(sortedData []int, tebakan int) (int, error) { + // jika data kosong, langsung return error karena tidak ada data yang dicari + if len(sortedData) == 0 { + return -1, ErrorMessage + } + + // inisialisasi batas bawah (low) dan atas (high) + // lowValue = nilai pada index low (elemen pertama) + // highValue = nilai pada index high (elemen terakhir) + var ( + low, high = 0, len(sortedData) - 1 + lowValue, highValue = sortedData[low], sortedData[high] + ) + + // - lowValue != highValue (untuk menghindari terjadinya pembagian dengan nol) + // - tebakan berada di antara lowValue dan highValue + // - lowValue <= tebakan && tebakan <= highValue (range valid) + for lowValue != highValue && (lowValue <= tebakan) && (tebakan <= highValue) { + // hitung posisi mid secara interpolasi + // rumus interpolasi + // mid = low + ((tebakan - lowValue) * (high - low)) / (highValue - lowValue) + // casting ke float64 digunakan untuk presisi hitungan pecahan + mid := low + int(float64(float64((tebakan-lowValue)*(high-low))/float64(highValue-lowValue))) + + // jika nilai di mid sama dengan tebakan, kita cek apakah ada duplikasi di + // sebelumnya + if sortedData[mid] == tebakan { + // geser ke kiri sampai ketemu elemen yang bukan tebakan + // ini nantinya dilakukan untuk menemukan indeks pertama dari nilai + // yang cocok + for mid > 0 && sortedData[mid-1] == tebakan { + mid-- + } + // setelah ketemu indeks pertama, return dari hasil + return mid, nil + } + + // jika nilai di mid lebih besar dari tebakan, geser batas atas + if sortedData[mid] > tebakan { + high, highValue = mid-1, sortedData[high] + } else { + // jika nilai di mid lebih kecil dari tebakan, geser batas bawah + low, lowValue = mid+1, sortedData[low] + } + } + + // setelah keluar dari looping, kita akan cek sekali lagi apakah + // lowValue == tebkan, karena bisa saja tebakan berada di tpat di lowValue + // setlah lowValue setelah iterasi selesai + if tebakan == lowValue { + return low, nil + } + // jika semua kondisi diatas tidak terpenuhi, maka nilai tidak ditemukan + return -1, ErrorMessage +} diff --git a/searching/interpolation_test.go b/searching/interpolation_test.go new file mode 100644 index 0000000..6e1e4ae --- /dev/null +++ b/searching/interpolation_test.go @@ -0,0 +1,12 @@ +package searching + +import "testing" + +func TestInterpolasi(t *testing.T) { + for _, test := range searchTests { + valueAktual, _ := pencarianInterpolasi(test.data, test.key) + if valueAktual != test.ekspetasi { + t.Errorf("test '%s' gagal: input array `%v` dengan key `%d`, ekspetasi `%d`, aktual: `%d`", test.name, test.data, test.key, test.ekspetasi, valueAktual) + } + } +} diff --git a/searching/linear_searching.go b/searching/linear_searching.go new file mode 100644 index 0000000..4116683 --- /dev/null +++ b/searching/linear_searching.go @@ -0,0 +1,28 @@ +package searching + +// melakukan pencarian secar linier (sequential searching) +// untuk menemukan kemunculan pertama dari nilai tertentu (query) di +// dalam array +// +// Parameter: +// - array []int: kumpulan data bertipe integer yang akan dicari +// - query int: nilai yang ingin dicari indeksnya dalam array +// +// Return: +// - int: indeks pertama tempat query ditemukan +// - error: jika query tidak ditemukan, maka throw error +func linearSearching(array []int, query int) (int, error) { + // melakukan iterasi terhadap array menggunakan array + // dimana `i` adalah indeks dan `item` adalah nilai pada posisi tersebut + for i, item := range array { + // jika elemen saat ini (item) sama dengan nilai yang dicari + // maka kembalikan indeksnya dan error nil + if item == query { + return i, nil + } + } + + // jika semua elemen telah dicek dan tidak ada yang cocok + // maka kembalikan -1 sebagai indikasi tidak diteumkan, dan throw ErrorMessage + return -1, ErrorMessage +} diff --git a/searching/linear_searching_test.go b/searching/linear_searching_test.go new file mode 100644 index 0000000..7232fb7 --- /dev/null +++ b/searching/linear_searching_test.go @@ -0,0 +1,12 @@ +package searching + +import "testing" + +func TestLinearSearching(t *testing.T) { + for _, test := range searchTests { + valueAktual, _ := linearSearching(test.data, test.key) + if valueAktual != test.ekspetasi { + t.Errorf("test `%s` gagal: input array `%v` dengan key `%d`, ekspetasi: `%d`, aktual: `%d`", test.name, test.data, test.key, test.ekspetasi, valueAktual) + } + } +} diff --git a/searching/ternary_searching.go b/searching/ternary_searching.go new file mode 100644 index 0000000..139f0a6 --- /dev/null +++ b/searching/ternary_searching.go @@ -0,0 +1,65 @@ +package searching + +import ( + "fmt" + "math" +) + +// ternaryMaxSearching mencari nilai maks lokal dari fungsi f(x) dalam interval [a, b] +// dengan toleransi epsilon menggunakan ternary searching +// +// algoritma +// - interval [a, b] dibagi menjadi tiga bagian yang sama panjang +// - evaluasi nilai f(x) pada dua titik tengah: kiri dan kanan +// - jika f(kiri) < f(kanan), maka nilai maks berada dis sisi kanan +// proses ini akan berlanjut secara rekursif hingga interval lebih kecil dari epsilon +// +// Parameter: +// a - batas kiri interval (float64) +// b - batas kanan interval (float64) +// epsilon - toleransi akurasi hasil (float64) +// f - fungsi matematis satu variable bertipe func(x float64) float64 +// +// Return: +// float64 - nilai maks dari f(x) dalam unterval [a, b] +// error - error jika input tidak valid atau terjadi masalah selama eksekusi +func ternaryMaxSearching(a, b, epsilon float64, f func(x float64) float64) (float64, error) { + if a == math.Inf(-1) || b == math.Inf(1) { + return -1, fmt.Errorf("interval harus memiliki angka float64") + } + if math.Abs(a-b) <= epsilon { + return f((a + b) / 2), nil + } + + kiri := (2*a + b) / 3 + kanan := (a + 2*b) / 3 + if f(kiri) < f(kanan) { + return ternaryMaxSearching(kiri, b, epsilon, f) + } + + return ternaryMaxSearching(a, kanan, epsilon, f) +} + +// ternaryMinSearching mencari nilai minimum lokal dari fungsi(x) dalam interval [a, b] +// +// algoritma: +// - interval [a, b] dibagi menjadi tiga bagian sama panjang +// - evaluasi nilai f(x) pada dua titik tengah: kiri dan kanan +// - jika f(kiri) > f(kanan), maka nilai minimum berbeda di sisi kanan +// proses berlanjut secara rekursif hingga interval lebih kecil dari epsilon +func ternaryMinSearching(a, b, epsilon float64, f func(x float64) float64) (float64, error) { + if a == math.Inf(-1) || b == math.Inf(1) { + return -1, fmt.Errorf("interval harus memiliki angka float64") + } + + if math.Abs(a-b) <= epsilon { + return f((a + b) / 2), nil + } + + kiri := (2*a + b) / 3 + kanan := (a + 2*b) / 3 + if f(kiri) > f(kanan) { + return ternaryMinSearching(kiri, b, epsilon, f) + } + return ternaryMinSearching(a, kanan, epsilon, f) +} diff --git a/searching/ternary_searching_test.go b/searching/ternary_searching_test.go new file mode 100644 index 0000000..2bd4c70 --- /dev/null +++ b/searching/ternary_searching_test.go @@ -0,0 +1,57 @@ +package searching + +import ( + "math" + "testing" +) + +const EPS = 1e-6 + +func equal(a, b float64) bool { + return math.Abs(a-b) <= EPS +} + +func TestTernaryMax(t *testing.T) { + var tests = []struct { + f func(x float64) float64 + a float64 + b float64 + ekspetasi float64 + }{ + {f: func(x float64) float64 { return -x * x }, a: 1, b: -1, ekspetasi: 0}, + } + + for _, test := range tests { + hasil, error := ternaryMaxSearching(test.a, test.b, EPS, test.f) + if error != nil { + t.Errorf("error, data: `%v`", error) + } + + if !equal(hasil, test.ekspetasi) { + t.Errorf("hasil salah, ekspetasi: `%v`, return aktual: `%v`", test.ekspetasi, hasil) + } + } +} + +func TestTernaryMin(t *testing.T) { + var tests = []struct { + f func(x float64) float64 + a float64 + b float64 + ekspetasi float64 + }{ + {f: func(x float64) float64 { return x * x }, a: -1, b: 1, ekspetasi: 0}, + {f: func(x float64) float64 { return 2*x*x + x + 1 }, a: -1, b: 1, ekspetasi: 0.875}, + } + + for _, test := range tests { + hasil, error := ternaryMinSearching(test.a, test.b, EPS, test.f) + if error != nil { + t.Errorf("error, data: `%v`", error) + } + + if !equal(hasil, test.ekspetasi) { + t.Errorf("hasil salah, ekspetasi: `%v`, return aktual: `%v`", test.ekspetasi, hasil) + } + } +} diff --git a/searching/utilities.go b/searching/utilities.go new file mode 100644 index 0000000..9d14d28 --- /dev/null +++ b/searching/utilities.go @@ -0,0 +1,26 @@ +package searching + +import "errors" + +var ErrorMessage = errors.New("target tidak terdapat pada array") + +type searchTest struct { + data []int + key int + ekspetasi int + ekspetasiError error + name string +} + +var searchTests = []searchTest{ + {[]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, 10, 9, nil, "Sanity"}, + {[]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, 9, 8, nil, "Sanity"}, + {[]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, 8, 7, nil, "Sanity"}, + {[]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, 7, 6, nil, "Sanity"}, + {[]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, 6, 5, nil, "Sanity"}, + {[]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, 5, 4, nil, "Sanity"}, + {[]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, 4, 3, nil, "Sanity"}, + {[]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, 3, 2, nil, "Sanity"}, + {[]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, 2, 1, nil, "Sanity"}, + {[]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, 1, 0, nil, "Sanity"}, +}