exercism/go/binary-search/binary_search_test.go

194 lines
4.9 KiB
Go

// Go has binary search in the standard library. Let's reimplement
// sort.SearchInts with the same API as documented in the standard library
// at http://golang.org/pkg/sort/#Search. Note that there are some differences
// with the exercise README.
//
// * If there are duplicate values of the key you are searching for, you can't
// just stop at the first one you find; you must find the first occurance in
// the slice.
//
// * There is no special "not found" indication. If the search key is not
// present, SearchInts returns the index of the first value greater than the
// search key. If the key is greater than all values in the slice, SearchInts
// returns the length of the slice.
//
// * You can assume the slice is sorted in increasing order. There is no need
// to check that it is sorted. (That would wreck the performance.)
//
// Try it on your own without peeking at the standard library code.
package binarysearch
import (
"math/rand"
"sort"
"testing"
)
var testData = []struct {
ref string
slice []int
key int
x int // expected result
}{
{"6 found at index 3",
[]int{1, 3, 4, 6, 8, 9, 11},
6, 3},
{"9 found at index 5",
[]int{1, 3, 4, 6, 8, 9, 11},
9, 5},
{"3 found at index 1",
[]int{1, 3, 5, 8, 13, 21, 34, 55, 89, 144},
3, 1},
{"55 found at index 7",
[]int{1, 3, 5, 8, 13, 21, 34, 55, 89, 144},
55, 7},
{"21 found at index 5",
[]int{1, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377},
21, 5},
{"34 found at index 6",
[]int{1, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377},
34, 6},
{"1 found at beginning of slice",
[]int{1, 3, 6},
1, 0},
{"6 found at end of slice",
[]int{1, 3, 6},
6, 2},
{"2 > 1 at index 0, < 3 at index 1",
[]int{1, 3, 6},
2, 1},
{"2 < all values",
[]int{11, 13, 16},
2, 0},
{"21 > all 3 values",
[]int{11, 13, 16},
21, 3},
{"1 found at beginning of slice",
[]int{1, 1, 1, 1, 1, 3, 6}, // duplicates
1, 0},
{"3 found at index 1",
[]int{1, 3, 3, 3, 3, 3, 6},
3, 1},
{"6 found at index 4",
[]int{1, 3, 3, 3, 6, 6, 6},
6, 4},
{"-2 > -3 at index 1, < -1 at index 2",
[]int{-6, -3, -1}, // negatives
-2, 2},
{"0 > -7 at index 4, < 1 at index 5",
[]int{-19, -17, -15, -12, -7, 1, 14, 35, 69, 124},
0, 5},
{"slice has no values",
nil, 0, 0},
}
func TestSearchInts(t *testing.T) {
for _, test := range testData {
if !sort.IntsAreSorted(test.slice) {
t.Skip("Invalid test data")
}
if x := SearchInts(test.slice, test.key); x != test.x {
t.Fatalf("SearchInts(%v, %d) = %d, want %d",
test.slice, test.key, x, test.x)
}
}
}
// Did you get it? Did you cut and paste from the standard library?
// Whatever. Now show you can work it.
//
// The test program will supply slices and keys and you will write a function
// that searches and returns one of the following messages:
//
// k found at beginning of slice.
// k found at end of slice.
// k found at index fx.
// k < all values.
// k > all n values.
// k > lv at lx, < gv at gx.
// slice has no values.
//
// In your messages, substitute appropritate values for k, lv, and gv;
// substitute indexes for fx, lx, and gx; substitute a number for n.
//
// In the function Message you will demonstrate a number of different ways
// to test the result of SearchInts. Note that you would probably never need
// all of these different tests in a real program. The point of the exercise
// is just to show that it is possible to identify a number of different
// conditions from the return value.
func TestMessage(t *testing.T) {
for _, test := range testData {
if !sort.IntsAreSorted(test.slice) {
t.Skip("Invalid test data")
}
if res := Message(test.slice, test.key); res != test.ref {
t.Fatalf("Message(%v, %d) =\n%q\nwant:\n%q",
test.slice, test.key, res, test.ref)
}
}
}
// Benchmarks also test searching larger random slices
func Benchmark1e2(b *testing.B) {
s := make([]int, 1e2)
for i := range s {
s[i] = rand.Intn(len(s))
}
sort.Ints(s)
k := rand.Intn(len(s))
ref := sort.SearchInts(s, k)
res := SearchInts(s, k)
if ref != res {
b.Fatalf(
"Search of %d values gave different answer than sort.SearchInts",
len(s))
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
SearchInts(s, k)
}
}
func Benchmark1e4(b *testing.B) {
s := make([]int, 1e4)
for i := range s {
s[i] = rand.Intn(len(s))
}
sort.Ints(s)
k := rand.Intn(len(s))
ref := sort.SearchInts(s, k)
res := SearchInts(s, k)
if ref != res {
b.Fatalf(
"Search of %d values gave different answer than sort.SearchInts",
len(s))
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
SearchInts(s, k)
}
}
func Benchmark1e6(b *testing.B) {
s := make([]int, 1e6)
for i := range s {
s[i] = rand.Intn(len(s))
}
sort.Ints(s)
k := rand.Intn(len(s))
ref := sort.SearchInts(s, k)
res := SearchInts(s, k)
if ref != res {
b.Fatalf(
"Search of %d values gave different answer than sort.SearchInts",
len(s))
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
SearchInts(s, k)
}
}