github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/util/search/search.go (about) 1 // Copyright 2018 The Cockroach Authors. 2 // 3 // Use of this software is governed by the Business Source License 4 // included in the file licenses/BSL.txt. 5 // 6 // As of the Change Date specified in that file, in accordance with 7 // the Business Source License, use of this software will be governed 8 // by the Apache License, Version 2.0, included in the file 9 // licenses/APL.txt. 10 11 package search 12 13 import "github.com/cockroachdb/errors" 14 15 // A Predicate is a funcation that returns whether a given search value "passes" 16 // or not. It assumes that that within the search domain of [min, max) provided 17 // to a Searcher, f(i) == true implies f(i-1) == true and f(i) == false implies 18 // f(i+1) == false. A Predicate can be called multiple times, so it should be 19 // a pure function. 20 type Predicate func(int) (bool, error) 21 22 // A Searcher searches to find the largest value that passes a given predicate 23 // function. 24 type Searcher interface { 25 // Search runs the predicate function multiple times while searching for the 26 // largest value that passes the provided predicate function. It is only 27 // valid to call Search once for a given Searcher instance. 28 Search(pred Predicate) (res int, err error) 29 30 // The following two methods are un-exported and are used by 31 // searchWithSearcher to provide a default implementation of Search. 32 33 // current returns the current value of the Searcher. 34 current() int 35 36 // step updates the Searcher with the results of a single search step. 37 step(pass bool) (found bool) 38 } 39 40 // Used to provide a default implementation of Searcher.Search. 41 func searchWithSearcher(s Searcher, pred Predicate) (int, error) { 42 for { 43 pass, err := pred(s.current()) 44 if err != nil { 45 return 0, err 46 } 47 found := s.step(pass) 48 if found { 49 return s.current(), nil 50 } 51 } 52 } 53 54 type searchSpace struct { 55 min int // max known passing 56 max int // min known failing 57 } 58 59 func (ss *searchSpace) bound(pass bool, cur, prec int) (bool, int) { 60 if prec < 1 { 61 panic(errors.Errorf("precision must be >= 1; found %d", prec)) 62 } 63 if pass { 64 if cur >= ss.max { 65 panic(errors.Errorf("passed at index above max; max=%v, cur=%v", ss.max, cur)) 66 } 67 ss.min = cur 68 } else { 69 if cur <= ss.min { 70 panic(errors.Errorf("failed at index below min; min=%v, cur=%v", ss.min, cur)) 71 } 72 ss.max = cur 73 } 74 if ss.max-ss.min <= prec { 75 return true, mid(ss.min, ss.max) 76 } 77 return false, 0 78 } 79 80 type binarySearcher struct { 81 ss searchSpace 82 cur int 83 prec int 84 } 85 86 // NewBinarySearcher returns a Searcher implementing a binary search strategy. 87 // Running the search predicate at min is assumed to pass and running the 88 // predicate at max is assumed to fail. 89 // 90 // While searching, it will result in a worst case and average case of O(log n) 91 // calls to the predicate function. 92 func NewBinarySearcher(min, max, prec int) Searcher { 93 if min >= max { 94 panic(errors.Errorf("min must be less than max; min=%v, max=%v", min, max)) 95 } 96 if prec < 1 { 97 panic(errors.Errorf("precision must be >= 1; prec=%v", prec)) 98 } 99 return &binarySearcher{ 100 ss: searchSpace{ 101 min: min, 102 max: max, 103 }, 104 cur: mid(min, max), 105 prec: prec, 106 } 107 } 108 109 func (bs *binarySearcher) Search(pred Predicate) (int, error) { 110 return searchWithSearcher(bs, pred) 111 } 112 113 func (bs *binarySearcher) current() int { return bs.cur } 114 115 func (bs *binarySearcher) step(pass bool) (found bool) { 116 found, val := bs.ss.bound(pass, bs.cur, bs.prec) 117 if found { 118 bs.cur = val 119 return true 120 } 121 122 bs.cur = mid(bs.ss.min, bs.ss.max) 123 return false 124 } 125 126 type lineSearcher struct { 127 ss searchSpace 128 cur int 129 stepSize int 130 firstStep bool 131 overshot bool 132 prec int 133 } 134 135 // NewLineSearcher returns a Searcher implementing a line search strategy with 136 // an adaptive step size. Running the search predicate at min is assumed to pass 137 // and running the predicate at max is assumed to fail. The strategy will begin 138 // searching at the provided start index and with the specified step size. 139 // 140 // While searching, it will result in a worst case of O(log n) calls to the 141 // predicate function. However, the average efficiency is dependent on the 142 // distance between the starting value and the desired value. If the initial 143 // guess is fairly accurate, the search strategy is expected to perform better 144 // (i.e. result in fewer steps) than performing a binary search with no a priori 145 // knowledge. 146 func NewLineSearcher(min, max, start, stepSize, prec int) Searcher { 147 if min >= max { 148 panic(errors.Errorf("min must be less than max; min=%v, max=%v", min, max)) 149 } 150 if start < min || start > max { 151 panic(errors.Errorf("start must be between (min, max); start=%v, min=%v, max=%v", 152 start, min, max)) 153 } 154 if stepSize < 1 { 155 panic(errors.Errorf("stepSize must be >= 1; stepSize=%v", stepSize)) 156 } 157 if prec < 1 { 158 panic(errors.Errorf("precision must be >= 1; prec=%v", prec)) 159 } 160 return &lineSearcher{ 161 ss: searchSpace{ 162 min: min, 163 max: max, 164 }, 165 cur: start, 166 stepSize: stepSize, 167 firstStep: true, 168 prec: prec, 169 } 170 } 171 172 func (ls *lineSearcher) Search(pred Predicate) (int, error) { 173 return searchWithSearcher(ls, pred) 174 } 175 176 func (ls *lineSearcher) current() int { return ls.cur } 177 178 func (ls *lineSearcher) step(pass bool) (found bool) { 179 found, val := ls.ss.bound(pass, ls.cur, ls.prec) 180 if found { 181 ls.cur = val 182 return true 183 } 184 185 neg := 1 186 if !pass { 187 neg = -1 188 } 189 if ls.firstStep { 190 // First step. Determine initial direction. 191 ls.firstStep = false 192 ls.stepSize = neg * ls.stepSize 193 } else if neg*ls.stepSize < 0 { 194 // Overshot. Reverse and decrease step size. 195 ls.overshot = true 196 ls.stepSize = -ls.stepSize / 2 197 } else { 198 // Undershot. 199 if ls.overshot { 200 // Already overshot. Continue decreasing step size. 201 ls.stepSize /= 2 202 } else { 203 // Haven't yet overshot. Increase step size. 204 ls.stepSize *= 2 205 } 206 } 207 208 // Don't exceed bounds. 209 minStep := ls.ss.min - ls.cur + 1 210 maxStep := ls.ss.max - ls.cur - 1 211 ls.stepSize = max(min(ls.stepSize, maxStep), minStep) 212 ls.cur = ls.cur + ls.stepSize 213 return false 214 } 215 216 func mid(a, b int) int { 217 return (a + b) / 2 218 } 219 220 func min(a, b int) int { 221 if a < b { 222 return a 223 } 224 return b 225 } 226 227 func max(a, b int) int { 228 if a > b { 229 return a 230 } 231 return b 232 }