Skip to content

Commit dcb018a

Browse files
committed
Add guesser
1 parent db64e90 commit dcb018a

File tree

4 files changed

+106
-11
lines changed

4 files changed

+106
-11
lines changed

README.md

+4-2
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,13 @@ See `sort!` for an explanation of the keyword arguments `by`, `lt` and `rev`.
4141
### `searchsortedfirstcorrelated(v::AbstractVector, x, guess)`
4242

4343
```julia
44-
searchsortedfirstcorrelated(v::AbstractVector{T}, x, guess::T)
44+
searchsortedfirstcorrelated(v::AbstractVector, x, guess)
4545
```
4646

4747
An accelerated `findfirst` on sorted vectors using a bracketed search. Requires a `guess`
48-
to start the search from.
48+
to start the search from, which is either an integer or an instance of `Guesser`.
49+
50+
An analogous function `searchsortedlastcorrelated` exists.
4951

5052

5153
Some benchmarks:

docs/src/index.md

+2
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@ Pkg.add("FindFirstFunctions")
1616
```@docs
1717
FindFirstFunctions.findfirstequal
1818
FindFirstFunctions.bracketstrictlymontonic
19+
FindFirstFunctions.Guesser
1920
FindFirstFunctions.searchsortedfirstcorrelated
21+
FindFirstFunctions.searchsortedlastcorrelated
2022
FindFirstFunctions.findfirstsortedequal
2123
```
2224

src/FindFirstFunctions.jl

+84-6
Original file line numberDiff line numberDiff line change
@@ -192,22 +192,100 @@ function bracketstrictlymontonic(
192192
end
193193

194194
"""
195-
searchsortedfirstcorrelated(v::AbstractVector{T}, x, guess)
195+
looks_linear(v; threshold = 1e-2)
196196
197-
An accelerated `findfirst` on sorted vectors using a bracketed search. Requires a `guess::T`
197+
Determine if the abscissae `v` are regularly distributed, taking the standard deviation of
198+
the difference between the array of abscissae with respect to the straight line linking
199+
its first and last elements, normalized by the range of `v`. If this standard deviation is
200+
below the given `threshold`, the vector looks linear (return true). Internal function -
201+
interface may change.
202+
"""
203+
function looks_linear(v; threshold = 1e-2)
204+
length(v) <= 2 && return true
205+
x_0, x_f = first(v), last(v)
206+
N = length(v)
207+
x_span = x_f - x_0
208+
mean_x_dist = x_span / (N - 1)
209+
norm_var =
210+
sum((x_i - x_0 - (i - 1) * mean_x_dist)^2 for (i, x_i) in enumerate(v)) /
211+
(N * x_span^2)
212+
norm_var < threshold^2
213+
end
214+
215+
"""
216+
Guesser(v::AbstractVector; looks_linear_threshold = 1e-2)
217+
218+
Wrapper of the searched vector `v` which makes an informed guess
219+
for `searchsorted*correlated` by either
220+
- Exploiting that `v` is sufficiently evenly spaced
221+
- Using the previous outcome of `searchsorted*correlated`
222+
"""
223+
struct Guesser{T<:AbstractVector}
224+
v::T
225+
idx_prev::Base.RefValue{Int}
226+
linear_lookup::Bool
227+
end
228+
229+
function Guesser(v::AbstractVector; looks_linear_threshold = 1e-2)
230+
Guesser(v, Ref(1), looks_linear(v; threshold = looks_linear_threshold))
231+
end
232+
233+
function (g::Guesser)(x)
234+
(; v, idx_prev, linear_lookup) = g
235+
if linear_lookup
236+
f = (x - first(v)) / (last(v) - first(v))
237+
if isinf(f)
238+
f > 0 ? lastindex(v) : firstindex(v)
239+
else
240+
i_0, i_f = firstindex(v), lastindex(v)
241+
round(typeof(firstindex(v)), f * (i_f - i_0) + i_0)
242+
end
243+
else
244+
idx_prev[]
245+
end
246+
end
247+
248+
"""
249+
searchsortedfirstcorrelated(v::AbstractVector, x, guess)
250+
251+
An accelerated `findfirst` on sorted vectors using a bracketed search. Requires a `guess::Union{<:Integer, Guesser}`
198252
to start the search from.
199253
"""
200-
function searchsortedfirstcorrelated(v::AbstractVector, x, guess)
254+
function searchsortedfirstcorrelated(v::AbstractVector, x, guess::T) where {T<:Integer}
201255
lo, hi = bracketstrictlymontonic(v, x, guess, Base.Order.Forward)
202256
searchsortedfirst(v, x, lo, hi, Base.Order.Forward)
203257
end
204258

205-
function searchsortedlastcorrelated(v::AbstractVector, x, guess)
259+
"""
260+
searchsortedlastcorrelated(v::AbstractVector{T}, x, guess)
261+
262+
An accelerated `findlast` on sorted vectors using a bracketed search. Requires a `guess::Union{<:Integer, Guesser}`
263+
to start the search from.
264+
"""
265+
function searchsortedlastcorrelated(v::AbstractVector, x, guess::T) where {T<:Integer}
206266
lo, hi = bracketstrictlymontonic(v, x, guess, Base.Order.Forward)
207267
searchsortedlast(v, x, lo, hi, Base.Order.Forward)
208268
end
209269

210-
searchsortedfirstcorrelated(r::AbstractRange, x, _) = searchsortedfirst(r, x)
211-
searchsortedlastcorrelated(r::AbstractRange, x, _) = searchsortedlast(r, x)
270+
searchsortedfirstcorrelated(r::AbstractRange, x, ::Integer) = searchsortedfirst(r, x)
271+
searchsortedlastcorrelated(r::AbstractRange, x, ::Integer) = searchsortedlast(r, x)
272+
273+
function searchsortedfirstcorrelated(
274+
v::AbstractVector,
275+
x,
276+
guess::Guesser{T},
277+
) where {T<:AbstractVector}
278+
@assert v === guess.v
279+
out = searchsortedfirstcorrelated(v, x, guess(x))
280+
guess.idx_prev[] = out
281+
out
282+
end
212283

284+
function searchsortedlastcorrelated(v::T, x, guess::Guesser{T}) where {T<:AbstractVector}
285+
@assert v === guess.v
286+
out = searchsortedlastcorrelated(v, x, guess(x))
287+
guess.idx_prev[] = out
288+
out
213289
end
290+
291+
end # module FindFirstFunctions

test/runtests.jl

+16-3
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,28 @@ using SafeTestsets, Test
1616
if length(x) > 0
1717
@test FindFirstFunctions.findfirstequal(x[begin], @view(x[begin:end])) === 1
1818
@test FindFirstFunctions.findfirstequal(x[begin], @view(x[begin+1:end])) ===
19-
nothing
19+
nothing
2020
@test FindFirstFunctions.findfirstequal(x[end], @view(x[begin:end-1])) ===
21-
nothing
21+
nothing
2222
end
2323
y = rand(Int)
2424
ff = findfirst(==(y), x)
2525
@test FindFirstFunctions.findfirstequal(y, x) === ff
26-
ff === nothing && @test FindFirstFunctions.findfirstsortedequal(y, x) === nothing
26+
ff === nothing &&
27+
@test FindFirstFunctions.findfirstsortedequal(y, x) === nothing
2728
end
2829

2930
end
31+
32+
@safetestset "Guesser" begin
33+
using FindFirstFunctions:
34+
Guesser, searchsortedfirstcorrelated, searchsortedlastcorrelated
35+
v = collect(LinRange(0, 10, 4))
36+
guesser_linear = Guesser(v)
37+
guesser_prev = Guesser(v, Ref(1), false)
38+
@test guesser_linear.linear_lookup
39+
@test searchsortedfirstcorrelated(v, 4.0, guesser_linear) == 3
40+
@test searchsortedlastcorrelated(v, 4.0, guesser_prev) == 2
41+
@test guesser_prev.idx_prev[] == 2
42+
end
3043
end

0 commit comments

Comments
 (0)