Guessers
A Guesser is a small wrapper around a sorted vector that produces an integer index hint for a given query, using one of two strategies decided at construction time:
- Linear-extrapolation lookup, when
vis approximately uniformly spaced. The guess isfirstindex(v) + round((x - v[1]) / (v[end] - v[1]) * (length(v) - 1)). Cost is O(1) per call, independent oflength(v). - Cached previous result, otherwise. The guesser hands back its
idx_prevfield. Useful for callers that issue temporally-correlated queries — the previous answer is a good seed forBracketGallop.
The Guesser does not perform the actual search. To use it as a search strategy, wrap it in GuesserHint.
Construction
FindFirstFunctions.Guesser — Type
Guesser(v::AbstractVector; looks_linear_threshold = 1e-2)Wrapper of the searched vector v which makes an informed guess for the next correlated lookup by either
- exploiting that
vis sufficiently evenly spaced (linear-extrapolation guess), or - using the previous outcome (the cached
idx_prev).
The threshold passed via looks_linear_threshold is the same threshold used by looks_linear (which the Guesser calls under the hood). The default 1e-2 accepts everything from exact linear ranges through evenly-spaced grids with mild jitter, but rejects log-spaced or piecewise-spaced data.
FindFirstFunctions.looks_linear — Function
looks_linear(v; threshold = 1e-2)Determine if the abscissae v are regularly distributed.
Calling a Guesser
guesser(x) returns an integer index hint, but does not update any state. The returned hint may be outside axes(v) if x is past either end of v; callers should treat the hint as advisory and clamp.
v = collect(0.0:0.1:10.0)
g = Guesser(v)
g(3.14) # → 32, an O(1) extrapolation guessFor irregular data the same call returns g.idx_prev[] — the last index written by a GuesserHint search (or 1 if no search has run yet).
Plugging into the strategy dispatch
GuesserHint is the strategy adapter that turns a Guesser into a SearchStrategy. It:
- Calls
guesser(x)to obtain an integer guess. - Dispatches to
BracketGallopfrom that guess. - Writes the resulting index back into
guesser.idx_prev.
FindFirstFunctions.GuesserHint — Type
GuesserHint(guesser::Guesser) <: SearchStrategyUses a Guesser to produce an integer guess for x, then dispatches to BracketGallop from that guess. The Guesser already decides between linear-extrapolation lookup and using the previous result as a guess; this strategy plugs that logic into the strategy dispatch hierarchy.
Stateful strategy.GuesserHint carries the Guesser (which carries idx_prev::Ref{Int} and linear_lookup::Bool), so it cannot be reduced to a StrategyKind tag. It dispatches via its own searchsorted_last(::GuesserHint, ...) / searchsorted_first(::GuesserHint, ...) methods.
Use this strategy with the per-query and batched APIs whenever you have a Guesser attached to a vector.
Per-call cost is one guesser(x) evaluation (O(1)) plus one BracketGallop (O(1) when the guess is close) plus one idx_prev[] write.
The vector passed to searchsortedfirst / searchsortedlast must be the same object the Guesser wraps — GuesserHint asserts v === s.guesser.v to catch the obvious misuse.
v = collect(0.0:0.1:10.0)
g = Guesser(v)
strat = GuesserHint(g)
i = searchsorted_last(strat, v, 3.14)
@assert g.idx_prev[] == i # the guesser caches the last resultGuesserHint ignores any externally-supplied hint — the Guesser carries its own hint state, and accepting a foreign hint would defeat the cache.
Pattern: correlated lookups for interpolation
The intended use is a wrapper struct that owns both the sorted vector and a matching Guesser. Every query against the wrapper feeds through GuesserHint:
struct Interp{V}
v::V
g::Guesser{V}
end
Interp(v) = Interp(v, Guesser(v))
function find_segment(itp::Interp, x)
return searchsorted_last(GuesserHint(itp.g), itp.v, x)
endAfter warmup, repeated calls to find_segment with temporally correlated x cost O(1): the previous result is one slot away from the current answer, so the BracketGallop inside GuesserHint returns after a couple of comparisons.
When not to use a Guesser
- One-shot queries. Construct cost is O(n) for the
looks_linearprobe — wasted if you only intend to search once. UseBinaryBracketor passAuto()with no hint. - Sorted batches. The batched
searchsortedlast!/searchsortedfirst!APIs already doprev_result-style hinting internally; they don't need a Guesser and they pick a faster strategy when the sweep is dense. - Strictly random queries against irregular
v.looks_linearreturns false, so the Guesser degrades to returningidx_prev[], which is useless for random queries.BracketGallopfromidx_previs fine butAuto-on-a-batch does the same thing without the Guesser overhead.