From 285298b975fd0d7490ea7cb7c7b7bf3e687988c6 Mon Sep 17 00:00:00 2001 From: Russ Cox Date: Thu, 18 Nov 2010 11:46:07 -0500 Subject: [PATCH] sort: invert meaning of f in Search Backwards incompatible change, but makes it easier to reason about non-idiomatic searches: now f specifies what is sought. R=gri CC=golang-dev https://golang.org/cl/3195042 --- src/pkg/go/token/position.go | 6 +++--- src/pkg/sort/search.go | 38 ++++++++++++++++++------------------ src/pkg/sort/search_test.go | 36 ++++++++++++++++++++++++---------- 3 files changed, 48 insertions(+), 32 deletions(-) diff --git a/src/pkg/go/token/position.go b/src/pkg/go/token/position.go index 03c353d136b..716a71beae2 100644 --- a/src/pkg/go/token/position.go +++ b/src/pkg/go/token/position.go @@ -94,7 +94,7 @@ func (p Pos) IsValid() bool { func searchFiles(a []*File, x int) int { - return sort.Search(len(a), func(i int) bool { return a[i].base <= x }) - 1 + return sort.Search(len(a), func(i int) bool { return a[i].base > x }) - 1 } @@ -252,12 +252,12 @@ func (f *File) Position(offset int) Position { func searchUints(a []int, x int) int { - return sort.Search(len(a), func(i int) bool { return a[i] <= x }) - 1 + return sort.Search(len(a), func(i int) bool { return a[i] > x }) - 1 } func searchLineInfos(a []lineInfo, x int) int { - return sort.Search(len(a), func(i int) bool { return a[i].offset <= x }) - 1 + return sort.Search(len(a), func(i int) bool { return a[i].offset > x }) - 1 } diff --git a/src/pkg/sort/search.go b/src/pkg/sort/search.go index b573ad17527..b3ddd2dfa81 100644 --- a/src/pkg/sort/search.go +++ b/src/pkg/sort/search.go @@ -7,11 +7,11 @@ package sort // Search uses binary search to find and return the smallest index i -// in [0, n) at which f(i) is false, assuming that on the range [0, n), -// f(i) == false implies f(i+1) == false. That is, Search requires that -// f is true for some (possibly empty) prefix of the input range [0, n) -// and then false for the (possibly empty) remainder; Search returns -// the first false index. If there is no such index, Search returns n. +// in [0, n) at which f(i) is true, assuming that on the range [0, n), +// f(i) == true implies f(i+1) == true. That is, Search requires that +// f is false for some (possibly empty) prefix of the input range [0, n) +// and then true for the (possibly empty) remainder; Search returns +// the first true index. If there is no such index, Search returns n. // Search calls f(i) only for i in the range [0, n). // // A common use of Search is to find the index i for a value x in @@ -21,19 +21,19 @@ package sort // ordered. // // For instance, given a slice data sorted in ascending order, -// the call Search(len(data), func(i int) bool { return data[i] < 23 }) +// the call Search(len(data), func(i int) bool { return data[i] >= 23 }) // returns the smallest index i such that data[i] >= 23. If the caller // wants to find whether 23 is in the slice, it must test data[i] == 23 // separately. // -// Searching data sorted in descending order would use the > -// operator instead of the < operator. +// Searching data sorted in descending order would use the <= +// operator instead of the >= operator. // // To complete the example above, the following code tries to find the value // x in an integer slice data sorted in ascending order: // // x := 23 -// i := sort.Search(len(data), func(i int) bool { return data[i] < x }) +// i := sort.Search(len(data), func(i int) bool { return data[i] >= x }) // if i < len(data) && data[i] == x { // // x is present at data[i] // } else { @@ -47,7 +47,7 @@ package sort // var s string // fmt.Printf("Pick an integer from 0 to 100.\n") // answer := sort.Search(100, func(i int) bool { -// fmt.Printf("Is your number > %d? ", i) +// fmt.Printf("Is your number <= %d? ", i) // fmt.Scanf("%s", &s) // return s != "" && s[0] == 'y' // }) @@ -55,19 +55,19 @@ package sort // } // func Search(n int, f func(int) bool) int { - // Define f(-1) == true and f(n) == false. - // Invariant: f(i-1) == true, f(j) == false. + // Define f(-1) == false and f(n) == true. + // Invariant: f(i-1) == false, f(j) == true. i, j := 0, n for i < j { h := i + (j-i)/2 // avoid overflow when computing h // i ≤ h < j - if f(h) { - i = h + 1 // preserves f(i-1) == true + if !f(h) { + i = h + 1 // preserves f(i-1) == false } else { - j = h // preserves f(j) == false + j = h // preserves f(j) == true } } - // i == j, f(i-1) == true, and f(j) (= f(i)) == false => answer is i. + // i == j, f(i-1) == false, and f(j) (= f(i)) == true => answer is i. return i } @@ -78,7 +78,7 @@ func Search(n int, f func(int) bool) int { // as specified by Search. The array must be sorted in ascending order. // func SearchInts(a []int, x int) int { - return Search(len(a), func(i int) bool { return a[i] < x }) + return Search(len(a), func(i int) bool { return a[i] >= x }) } @@ -86,7 +86,7 @@ func SearchInts(a []int, x int) int { // as specified by Search. The array must be sorted in ascending order. // func SearchFloats(a []float, x float) int { - return Search(len(a), func(i int) bool { return a[i] < x }) + return Search(len(a), func(i int) bool { return a[i] >= x }) } @@ -94,7 +94,7 @@ func SearchFloats(a []float, x float) int { // as specified by Search. The array must be sorted in ascending order. // func SearchStrings(a []string, x string) int { - return Search(len(a), func(i int) bool { return a[i] < x }) + return Search(len(a), func(i int) bool { return a[i] >= x }) } diff --git a/src/pkg/sort/search_test.go b/src/pkg/sort/search_test.go index 10dbd3a75ab..e16e2c93fb7 100644 --- a/src/pkg/sort/search_test.go +++ b/src/pkg/sort/search_test.go @@ -9,7 +9,7 @@ import "testing" func f(a []int, x int) func(int) bool { return func(i int) bool { - return a[i] < x + return a[i] >= x } } @@ -23,12 +23,12 @@ var tests = []struct { i int }{ {"empty", 0, nil, 0}, - {"1 1", 1, func(i int) bool { return i < 1 }, 1}, - {"1 false", 1, func(i int) bool { return false }, 0}, - {"1 true", 1, func(i int) bool { return true }, 1}, - {"1e9 991", 1e9, func(i int) bool { return i < 991 }, 991}, - {"1e9 false", 1e9, func(i int) bool { return false }, 0}, - {"1e9 true", 1e9, func(i int) bool { return true }, 1e9}, + {"1 1", 1, func(i int) bool { return i >= 1 }, 1}, + {"1 true", 1, func(i int) bool { return true }, 0}, + {"1 false", 1, func(i int) bool { return false }, 1}, + {"1e9 991", 1e9, func(i int) bool { return i >= 991 }, 991}, + {"1e9 true", 1e9, func(i int) bool { return true }, 0}, + {"1e9 false", 1e9, func(i int) bool { return false }, 1e9}, {"data -20", len(data), f(data, -20), 0}, {"data -10", len(data), f(data, -10), 0}, {"data -9", len(data), f(data, -9), 1}, @@ -41,8 +41,9 @@ var tests = []struct { {"data 101", len(data), f(data, 101), 12}, {"data 10000", len(data), f(data, 10000), 13}, {"data 10001", len(data), f(data, 10001), 14}, - {"descending a", 7, func(i int) bool { return []int{99, 99, 59, 42, 7, 0, -1, -1}[i] > 7 }, 4}, - {"descending 7", 1e9, func(i int) bool { return 1e9-i > 7 }, 1e9 - 7}, + {"descending a", 7, func(i int) bool { return []int{99, 99, 59, 42, 7, 0, -1, -1}[i] <= 7 }, 4}, + {"descending 7", 1e9, func(i int) bool { return 1e9-i <= 7 }, 1e9 - 7}, + {"overflow", 2e9, func(i int) bool { return false }, 2e9}, } @@ -79,7 +80,7 @@ func TestSearchEfficiency(t *testing.T) { max := log2(n) for x := 0; x < n; x += step { count := 0 - i := Search(n, func(i int) bool { count++; return i < x }) + i := Search(n, func(i int) bool { count++; return i >= x }) if i != x { t.Errorf("n = %d: expected index %d; got %d", n, x, i) } @@ -119,3 +120,18 @@ func TestSearchWrappers(t *testing.T) { } } } + + +// Abstract exhaustive test: all sizes up to 100, +// all possible return values. If there are any small +// corner cases, this test exercises them. +func TestSearchExhaustive(t *testing.T) { + for size := 0; size <= 100; size++ { + for targ := 0; targ <= size; targ++ { + i := Search(size, func(i int) bool { return i >= targ }) + if i != targ { + t.Errorf("Search(%d, %d) = %d", size, targ, i) + } + } + } +}