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  }