Skip to content

Commit 8ebc90e

Browse files
committed
merge master
2 parents 8bb2b8c + 7545efd commit 8ebc90e

File tree

4 files changed

+165
-1
lines changed

4 files changed

+165
-1
lines changed

.github/workflows/Downstream.yml

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
name: IntegrationTest
2+
on:
3+
push:
4+
branches: [master]
5+
tags: [v*]
6+
pull_request:
7+
8+
jobs:
9+
test:
10+
name: ${{ matrix.package.repo }}/${{ matrix.package.group }}/${{ matrix.julia-version }}
11+
runs-on: ${{ matrix.os }}
12+
env:
13+
GROUP: ${{ matrix.package.group }}
14+
strategy:
15+
fail-fast: false
16+
matrix:
17+
julia-version: [1]
18+
os: [ubuntu-latest]
19+
package:
20+
- {user: SciML, repo: DataInterpolations.jl, group: Core}
21+
- {user: SciML, repo: ModelingToolkit.jl, group: InterfaceI}
22+
steps:
23+
- uses: actions/checkout@v4
24+
- uses: julia-actions/setup-julia@v1
25+
with:
26+
version: ${{ matrix.julia-version }}
27+
arch: x64
28+
- uses: julia-actions/julia-buildpkg@latest
29+
- name: Clone Downstream
30+
uses: actions/checkout@v4
31+
with:
32+
repository: ${{ matrix.package.user }}/${{ matrix.package.repo }}
33+
path: downstream
34+
- name: Load this and run the downstream tests
35+
shell: julia --color=yes --project=downstream {0}
36+
run: |
37+
using Pkg
38+
try
39+
# force it to use this PR's version of the package
40+
Pkg.develop(PackageSpec(path=".")) # resolver may fail with main deps
41+
Pkg.update()
42+
Pkg.test(coverage=true) # resolver may fail with test time deps
43+
catch err
44+
err isa Pkg.Resolve.ResolverError || rethrow()
45+
# If we can't resolve that means this is incompatible by SemVer and this is fine
46+
# It means we marked this as a breaking change, so we don't need to worry about
47+
# Mistakenly introducing a breaking change, as we have intentionally made one
48+
@info "Not compatible with this release. No problem." exception=err
49+
exit(0) # Exit immediately, as a success
50+
end
51+
- uses: julia-actions/julia-processcoverage@v1
52+
- uses: codecov/codecov-action@v3
53+
with:
54+
file: lcov.info

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "FindFirstFunctions"
22
uuid = "64ca27bc-2ba2-4a57-88aa-44e436879224"
33
authors = ["Chris Elrod <[email protected]> and contributors"]
4-
version = "1.0.0"
4+
version = "1.1.0"
55

66
[compat]
77
julia = "1.8"

README.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,48 @@
11
# FindFirstFunctions
22

33
[![Build Status](https://github.com/SciML/FindFirstFunctions.jl/actions/workflows/CI.yml/badge.svg?branch=main)](https://github.com/SciML/FindFirstFunctions.jl/actions/workflows/CI.yml?query=branch%3Amain)
4+
5+
FindFirstFunctions.jl is a package for faster `findfirst` type functions. These are specailized to improve performance
6+
over more generic implementations.
7+
8+
## Functions
9+
10+
### `findfirstequal`
11+
12+
```julia
13+
findfirstequal(x::Int64,A::DenseVector{Int64})
14+
```
15+
16+
Finds the first value in `A` equal to `x`
17+
18+
### `bracketstrictlymontonic`
19+
20+
```julia
21+
bracketstrictlymontonic(v, x, guess; lt=<comparison>, by=<transform>, rev=false)
22+
```
23+
24+
Starting from an initial `guess` index, find indices `(lo, hi)` such that `v[lo] ≤ x ≤
25+
v[hi]` according to the specified order, assuming that `x` is actually within the range of
26+
values found in `v`. If `x` is outside that range, either `lo` will be `firstindex(v)` or
27+
`hi` will be `lastindex(v)`.
28+
29+
Note that the results will not typically satisfy `lo ≤ guess ≤ hi`. If `x` is precisely
30+
equal to a value that is not unique in the input `v`, there is no guarantee that `(lo, hi)`
31+
will encompass *all* indices corresponding to that value.
32+
33+
This algorithm is essentially an expanding binary search, which can be used as a precursor
34+
to `searchsorted` and related functions, which can take `lo` and `hi` as arguments. The
35+
purpose of using this function first would be to accelerate convergence in those functions
36+
by using correlated `guess`es for repeated calls. The best `guess` for the next call of
37+
this function would be the index returned by the previous call to `searchsorted`.
38+
39+
See `sort!` for an explanation of the keyword arguments `by`, `lt` and `rev`.
40+
41+
### `searchsortedfirstcorrelated(v::AbstractVector, x, guess)`
42+
43+
```julia
44+
searchsortedfirstcorrelated(v::AbstractVector, x, guess)
45+
```
46+
47+
An accelerated `findfirst` on sorted vectors using a bracketed search. Requires a `guess`
48+
to start the search from.

src/FindFirstFunctions.jl

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,5 +95,70 @@ function findfirstsortedequal(var::Int64, vars::DenseVector{Int64},
9595
end
9696

9797

98+
"""
99+
bracketstrictlymontonic(v, x, guess; lt=<comparison>, by=<transform>, rev=false)
100+
101+
Starting from an initial `guess` index, find indices `(lo, hi)` such that `v[lo] ≤ x ≤
102+
v[hi]` according to the specified order, assuming that `x` is actually within the range of
103+
values found in `v`. If `x` is outside that range, either `lo` will be `firstindex(v)` or
104+
`hi` will be `lastindex(v)`.
105+
106+
Note that the results will not typically satisfy `lo ≤ guess ≤ hi`. If `x` is precisely
107+
equal to a value that is not unique in the input `v`, there is no guarantee that `(lo, hi)`
108+
will encompass *all* indices corresponding to that value.
109+
110+
This algorithm is essentially an expanding binary search, which can be used as a precursor
111+
to `searchsorted` and related functions, which can take `lo` and `hi` as arguments. The
112+
purpose of using this function first would be to accelerate convergence in those functions
113+
by using correlated `guess`es for repeated calls. The best `guess` for the next call of
114+
this function would be the index returned by the previous call to `searchsorted`.
115+
116+
See [`sort!`](@ref) for an explanation of the keyword arguments `by`, `lt` and `rev`.
117+
"""
118+
function bracketstrictlymontonic(v::AbstractVector,
119+
x,
120+
guess::T,
121+
o::Base.Order.Ordering)::NTuple{2,keytype(v)} where {T<:Integer}
122+
bottom = firstindex(v)
123+
top = lastindex(v)
124+
if guess < bottom || guess > top
125+
return bottom, top
126+
# # NOTE: for cache efficiency in repeated calls, we avoid accessing the first and last elements of `v`
127+
# # on each call to this function. This should only result in significant slow downs for calls with
128+
# # out-of-bounds values of `x` *and* bad `guess`es.
129+
# elseif lt(o, x, v[bottom])
130+
# return bottom, bottom
131+
# elseif lt(o, v[top], x)
132+
# return top, top
133+
else
134+
u = T(1)
135+
lo, hi = guess, min(guess + u, top)
136+
@inbounds if Base.Order.lt(o, x, v[lo])
137+
while lo > bottom && Base.Order.lt(o, x, v[lo])
138+
lo, hi = max(bottom, lo - u), lo
139+
u += u
140+
end
141+
else
142+
while hi < top && !Base.Order.lt(o, x, v[hi])
143+
lo, hi = hi, min(top, hi + u)
144+
u += u
145+
end
146+
end
147+
end
148+
return lo, hi
149+
end
150+
151+
function searchsortedfirstcorrelated(v::AbstractVector, x, guess)
152+
lo, hi = bracketstrictlymontonic(v, x, guess, Base.Order.Forward)
153+
searchsortedfirst(v, x, lo, hi, Base.Order.Forward)
154+
end
155+
156+
function searchsortedlastcorrelated(v::AbstractVector, x, guess)
157+
lo, hi = bracketstrictlymontonic(v, x, guess, Base.Order.Forward)
158+
searchsortedlast(v, x, lo, hi, Base.Order.Forward)
159+
end
160+
161+
searchsortedfirstcorrelated(r::AbstractRange, x, _) = searchsortedfirst(r, x)
162+
searchsortedlastcorrelated(r::AbstractRange, x, _) = searchsortedlast(r, x)
98163

99164
end

0 commit comments

Comments
 (0)