194 lines
4.9 KiB
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)
|
|
}
|
|
}
|