Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add efficient findfirst method for StepRange #30778

Merged
merged 2 commits into from
Jan 23, 2019

Conversation

marekkukan-tw
Copy link
Contributor

Several functions like sum, reverse, etc. have efficient (O(1)) methods for ranges implemented in Base.
This PR adds such method for findfirst, where the predicate is equality, and the iterable is a StepRange.

before

julia> @btime findfirst(==(10^5), 1:3:10^6)
  27.386 μs (3 allocations: 64 bytes)
33334

after

julia> @btime findfirst(==(10^5), 1:3:10^6)
  15.881 ns (0 allocations: 0 bytes)
33334

@JeffBezanson
Copy link
Sponsor Member

Thanks, @marekkukan-tw , and welcome.

The issue here is that Fix2 is not defined yet when range.jl is loaded. For now, you could put the method in array.jl with other findfirst methods. Please also add a test in test/ranges.jl, or confirm that the new method is already exercised by existing tests.

@marekkukan-tw
Copy link
Contributor Author

marekkukan-tw commented Jan 22, 2019

Thank you @JeffBezanson .
I used Revise.track(Base) and didn't realize there was this issue.
This time I ran make clean testall so hopefully it's OK now.

@@ -1741,6 +1741,13 @@ end
findfirst(testf::Function, A::Union{AbstractArray, AbstractString}) =
findnext(testf, A, first(keys(A)))

function findfirst(p::Union{Fix2{typeof(isequal),T},Fix2{typeof(==),T}}, r::StepRange{T,S}) where {T,S}
Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should perhaps be restricted to Integer types. It's tricky to know whether this formula will hit a floating point value exactly; see for example the _in_range function in this file. You have to check that the value in the range actually equals what you're looking for. Restricting to Integers also makes it easy to allow different types; the signature can be

findfirst(p::Union{Fix2{typeof(isequal),<:Integer},Fix2{typeof(==),<:Integer}}, r::StepRange{<:Integer,S}) where {S}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My actual use case was with DateTime objects, for example:

julia> @btime findfirst(==(DateTime(2019, 1, 23)), DateTime(2017):Second(1):DateTime(2020)) # before
  53.385 ms (3 allocations: 64 bytes)
64972801

julia> @btime findfirst(==(DateTime(2019, 1, 23)), DateTime(2017):Second(1):DateTime(2020)) # after
  64.659 ns (0 allocations: 0 bytes)
64972801

The fact that T occurs in StepRange{T,S} should restrict it to discrete types.

Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. It's too bad that the method won't match for different types of integers, but I guess this is ok for now.

@mcabbott
Copy link
Contributor

It would be nice if something like this method, or even simpler, was called for UnitRanges too:

julia> @btime findfirst(isequal(999), 1:1000);
  563.616 ns (0 allocations: 0 bytes)

julia> @btime findfirst(isequal(999), Base.OneTo(1000));
  563.892 ns (0 allocations: 0 bytes)

julia> @btime findfirst(isequal(999), 1:1:1000); # this method
  0.030 ns (0 allocations: 0 bytes)

@nalimilan
Copy link
Member

This causes issues for ranges with a Year step, see #35203.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants