Skip to content

Commit 80e4d44

Browse files
committed
Use more generic approach for findfirst and findlast
Also add tests, and remove @inferred calls which did not actually check whether functions were inferred, but only whether the equality test was. Change the _pairs() fallback to use Iterators.countfrom() so that an error is raised by findlast() rather than assuming the last index is typemax(Int), and use length() for iterators which support it so that findlast() works.
1 parent 8658ad2 commit 80e4d44

File tree

5 files changed

+160
-40
lines changed

5 files changed

+160
-40
lines changed

base/array.jl

+81-21
Original file line numberDiff line numberDiff line change
@@ -1492,6 +1492,11 @@ cat(n::Integer, x::Integer...) = reshape([x...], (ntuple(x->1, n-1)..., length(x
14921492

14931493
## find ##
14941494

1495+
_pairs(A::Union{AbstractArray, AbstractDict, AbstractString, Tuple, NamedTuple}) = pairs(A)
1496+
_pairs(iter) = _pairs(IteratorSize(iter), iter)
1497+
_pairs(::Union{HasLength, HasShape}, iter) = zip(1:length(iter), iter)
1498+
_pairs(::Union{SizeUnknown, IsInfinite}, iter) = zip(Iterators.countfrom(1), iter)
1499+
14951500
"""
14961501
findnext(A, i::Integer)
14971502
@@ -1545,12 +1550,14 @@ end
15451550
"""
15461551
findfirst(A)
15471552
1548-
Return the index of the first `true` value in `A`.
1553+
Return the index or key of the first `true` value in `A`.
15491554
Return `nothing` if no such value is found.
15501555
To search for other kinds of values, pass a predicate as the first argument.
15511556
1552-
Indices are of the same type as those returned by [`keys(A)`](@ref)
1553-
and [`pairs(A)`](@ref).
1557+
Indices or keys are of the same type as those returned by [`keys(A)`](@ref)
1558+
and [`pairs(A)`](@ref) for `AbstractArray`, `AbstractDict`, `AbstractString`
1559+
`Tuple` and `NamedTuple` objects, and are linear indices starting at `1`
1560+
for other iterables.
15541561
15551562
# Examples
15561563
```jldoctest
@@ -1576,7 +1583,22 @@ julia> findfirst(A)
15761583
CartesianIndex(2, 1)
15771584
```
15781585
"""
1579-
findfirst(A) = isempty(A) ? nothing : findnext(A, first(keys(A)))
1586+
function findfirst(A)
1587+
warned = false
1588+
for (i, a) in _pairs(A)
1589+
if !warned && !(a isa Bool)
1590+
depwarn("In the future `findfirst` will only work on boolean collections. Use `findfirst(x->x!=0, A)` instead.", :findfirst)
1591+
warned = true
1592+
end
1593+
if a != 0
1594+
return i
1595+
end
1596+
end
1597+
return nothing
1598+
end
1599+
1600+
# Needed for bootstrap, and allows defining only an optimized findnext method
1601+
findfirst(A::Union{AbstractArray, AbstractString}) = findnext(A, first(keys(A)))
15801602

15811603
"""
15821604
findnext(predicate::Function, A, i)
@@ -1626,11 +1648,13 @@ end
16261648
"""
16271649
findfirst(predicate::Function, A)
16281650
1629-
Return the index of the first element of `A` for which `predicate` returns `true`.
1651+
Return the index or key of the first element of `A` for which `predicate` returns `true`.
16301652
Return `nothing` if there is no such element.
16311653
1632-
Indices are of the same type as those returned by [`keys(A)`](@ref)
1633-
and [`pairs(A)`](@ref).
1654+
Indices or keys are of the same type as those returned by [`keys(A)`](@ref)
1655+
and [`pairs(A)`](@ref) for `AbstractArray`, `AbstractDict`, `AbstractString`
1656+
`Tuple` and `NamedTuple` objects, and are linear indices starting at `1`
1657+
for other iterables.
16341658
16351659
# Examples
16361660
```jldoctest
@@ -1659,7 +1683,16 @@ julia> findfirst(iseven, A)
16591683
CartesianIndex(2, 1)
16601684
```
16611685
"""
1662-
findfirst(testf::Function, A) = isempty(A) ? nothing : findnext(testf, A, first(keys(A)))
1686+
function findfirst(testf::Function, A)
1687+
for (i, a) in _pairs(A)
1688+
testf(a) && return i
1689+
end
1690+
return nothing
1691+
end
1692+
1693+
# Needed for bootstrap, and allows defining only an optimized findnext method
1694+
findfirst(testf::Function, A::Union{AbstractArray, AbstractString}) =
1695+
findnext(testf, A, first(keys(A)))
16631696

16641697
"""
16651698
findprev(A, i)
@@ -1711,11 +1744,13 @@ end
17111744
"""
17121745
findlast(A)
17131746
1714-
Return the index of the last `true` value in `A`.
1747+
Return the index or key of the last `true` value in `A`.
17151748
Return `nothing` if there is no `true` value in `A`.
17161749
1717-
Indices are of the same type as those returned by [`keys(A)`](@ref)
1718-
and [`pairs(A)`](@ref).
1750+
Indices or keys are of the same type as those returned by [`keys(A)`](@ref)
1751+
and [`pairs(A)`](@ref) for `AbstractArray`, `AbstractDict`, `AbstractString`
1752+
`Tuple` and `NamedTuple` objects, and are linear indices starting at `1`
1753+
for other iterables.
17191754
17201755
# Examples
17211756
```jldoctest
@@ -1743,7 +1778,22 @@ julia> findlast(A)
17431778
CartesianIndex(2, 1)
17441779
```
17451780
"""
1746-
findlast(A) = isempty(A) ? nothing : findprev(A, last(keys(A)))
1781+
function findlast(A)
1782+
warned = false
1783+
for (i, a) in Iterators.reverse(_pairs(A))
1784+
if !warned && !(a isa Bool)
1785+
depwarn("In the future `findlast` will only work on boolean collections. Use `findlast(x->x!=0, A)` instead.", :findlast)
1786+
warned = true
1787+
end
1788+
if a != 0
1789+
return i
1790+
end
1791+
end
1792+
return nothing
1793+
end
1794+
1795+
# Needed for bootstrap, and allows defining only an optimized findprev method
1796+
findlast(A::Union{AbstractArray, AbstractString}) = findprev(A, last(keys(A)))
17471797

17481798
"""
17491799
findprev(predicate::Function, A, i)
@@ -1790,11 +1840,13 @@ end
17901840
"""
17911841
findlast(predicate::Function, A)
17921842
1793-
Return the index of the last element of `A` for which `predicate` returns `true`.
1843+
Return the index or key of the last element of `A` for which `predicate` returns `true`.
17941844
Return `nothing` if there is no such element.
17951845
1796-
Indices are of the same type as those returned by [`keys(A)`](@ref)
1797-
and [`pairs(A)`](@ref).
1846+
Indices or keys are of the same type as those returned by [`keys(A)`](@ref)
1847+
and [`pairs(A)`](@ref) for `AbstractArray`, `AbstractDict`, `AbstractString`
1848+
`Tuple` and `NamedTuple` objects, and are linear indices starting at `1`
1849+
for other iterables.
17981850
17991851
# Examples
18001852
```jldoctest
@@ -1820,16 +1872,27 @@ julia> findlast(isodd, A)
18201872
CartesianIndex(2, 1)
18211873
```
18221874
"""
1823-
findlast(testf::Function, A) = isempty(A) ? nothing : findprev(testf, A, last(keys(A)))
1875+
function findlast(testf::Function, A)
1876+
for (i, a) in Iterators.reverse(_pairs(A))
1877+
testf(a) && return i
1878+
end
1879+
return nothing
1880+
end
1881+
1882+
# Needed for bootstrap, and allows defining only an optimized findprev method
1883+
findlast(testf::Function, A::Union{AbstractArray, AbstractString}) =
1884+
findprev(testf, A, last(keys(A)))
18241885

18251886
"""
18261887
findall(f::Function, A)
18271888
18281889
Return a vector `I` of the indices or keys of `A` where `f(A[I])` returns `true`.
18291890
If there are no such elements of `A`, return an empty array.
18301891
1831-
Indices are of the same type as those returned by [`keys(A)`](@ref)
1832-
and [`pairs(A)`](@ref).
1892+
Indices or keys are of the same type as those returned by [`keys(A)`](@ref)
1893+
and [`pairs(A)`](@ref) for `AbstractArray`, `AbstractDict`, `AbstractString`
1894+
`Tuple` and `NamedTuple` objects, and are linear indices starting at `1`
1895+
for other iterables.
18331896
18341897
# Examples
18351898
```jldoctest
@@ -1875,9 +1938,6 @@ julia> findall(x -> x >= 0, d)
18751938
"""
18761939
findall(testf::Function, A) = collect(first(p) for p in _pairs(A) if testf(last(p)))
18771940

1878-
_pairs(A::Union{AbstractArray, AbstractDict, AbstractString, Tuple, NamedTuple}) = pairs(A)
1879-
_pairs(iter) = zip(OneTo(typemax(Int)), iter) # safe for objects that don't implement length
1880-
18811941
"""
18821942
findall(A)
18831943

test/arrayops.jl

+36-5
Original file line numberDiff line numberDiff line change
@@ -460,18 +460,49 @@ end
460460
@test findnext(equalto(0x00), [0x00, 0x01, 0x00], 2) == 3
461461
@test findprev(equalto(0x00), [0x00, 0x01, 0x00], 2) == 1
462462
end
463-
@testset "findall with Matrix" begin
463+
@testset "find with Matrix" begin
464464
A = [1 2 0; 3 4 0]
465465
@test findall(isodd, A) == [CartesianIndex(1, 1), CartesianIndex(2, 1)]
466466
@test findall(!iszero, A) == [CartesianIndex(1, 1), CartesianIndex(2, 1),
467467
CartesianIndex(1, 2), CartesianIndex(2, 2)]
468-
end
469-
@testset "findall with general iterables" begin
468+
@test findfirst(isodd, A) == CartesianIndex(1, 1)
469+
@test findlast(isodd, A) == CartesianIndex(2, 1)
470+
@test findnext(isodd, A, CartesianIndex(1, 1)) == CartesianIndex(1, 1)
471+
@test findprev(isodd, A, CartesianIndex(2, 1)) == CartesianIndex(2, 1)
472+
@test findnext(isodd, A, CartesianIndex(1, 2)) === nothing
473+
@test findprev(iseven, A, CartesianIndex(2, 1)) === nothing
474+
end
475+
@testset "find with general iterables" begin
470476
s = "julia"
471477
@test findall(c -> c == 'l', s) == [3]
478+
@test findfirst(c -> c == 'l', s) == 3
479+
@test findlast(c -> c == 'l', s) == 3
480+
@test findnext(c -> c == 'l', s, 1) == 3
481+
@test findprev(c -> c == 'l', s, 5) == 3
482+
@test findnext(c -> c == 'l', s, 4) === nothing
483+
@test findprev(c -> c == 'l', s, 2) === nothing
484+
472485
g = Base.Unicode.graphemes("日本語")
473-
@test findall(isascii, g) == Int[]
474-
@test findall(!iszero, (i % 2 for i in 1:10)) == 1:2:9
486+
@test findall(!isempty, g) == 1:3
487+
@test isempty(findall(isascii, g))
488+
@test findfirst(!isempty, g) == 1
489+
@test findfirst(isascii, g) === nothing
490+
# Check that the last index isn't assumed to be typemax(Int)
491+
@test_throws MethodError findlast(!iszero, g)
492+
493+
g2 = (i % 2 for i in 1:10)
494+
@test findall(!iszero, g2) == 1:2:9
495+
@test findfirst(!iszero, g2) == 1
496+
@test findlast(!iszero, g2) == 9
497+
@test findfirst(equalto(2), g2) === nothing
498+
@test findlast(equalto(2), g2) === nothing
499+
500+
g3 = (i % 2 for i in 1:10, j in 1:2)
501+
@test findall(!iszero, g3) == 1:2:19
502+
@test findfirst(!iszero, g3) == 1
503+
@test findlast(!iszero, g3) == 19
504+
@test findfirst(equalto(2), g3) === nothing
505+
@test findlast(equalto(2), g3) === nothing
475506
end
476507

477508
@testset "findmin findmax indmin indmax" begin

test/dict.jl

+10-5
Original file line numberDiff line numberDiff line change
@@ -760,9 +760,14 @@ end
760760
@test map(string, keys(d)) == Set(["1","3"])
761761
end
762762

763-
@testset "findall" begin
764-
@test @inferred findall(equalto(1), Dict(:a=>1, :b=>2)) == [:a]
765-
@test @inferred sort(findall(equalto(1), Dict(:a=>1, :b=>1))) == [:a, :b]
766-
@test @inferred isempty(findall(equalto(1), Dict()))
767-
@test @inferred isempty(findall(equalto(1), Dict(:a=>2, :b=>3)))
763+
@testset "find" begin
764+
@test findall(equalto(1), Dict(:a=>1, :b=>2)) == [:a]
765+
@test sort(findall(equalto(1), Dict(:a=>1, :b=>1))) == [:a, :b]
766+
@test isempty(findall(equalto(1), Dict()))
767+
@test isempty(findall(equalto(1), Dict(:a=>2, :b=>3)))
768+
769+
@test findfirst(equalto(1), Dict(:a=>1, :b=>2)) == :a
770+
@test findfirst(equalto(1), Dict(:a=>1, :b=>1, :c=>3)) in (:a, :b)
771+
@test findfirst(equalto(1), Dict()) === nothing
772+
@test findfirst(equalto(1), Dict(:a=>2, :b=>3)) === nothing
768773
end

test/namedtuple.jl

+12-4
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,15 @@ abstr_nt_22194_3()
209209
@test typeof(Base.structdiff(NamedTuple{(:a, :b), Tuple{Int32, Union{Int32, Nothing}}}((1, Int32(2))),
210210
(a=0,))) === NamedTuple{(:b,), Tuple{Union{Int32, Nothing}}}
211211

212-
@test @inferred findall(equalto(1), (a=1, b=2)) == [:a]
213-
@test @inferred findall(equalto(1), (a=1, b=1)) == [:a, :b]
214-
@test @inferred isempty(findall(equalto(1), NamedTuple()))
215-
@test @inferred isempty(findall(equalto(1), (a=2, b=3)))
212+
@test findall(equalto(1), (a=1, b=2)) == [:a]
213+
@test findall(equalto(1), (a=1, b=1)) == [:a, :b]
214+
@test isempty(findall(equalto(1), NamedTuple()))
215+
@test isempty(findall(equalto(1), (a=2, b=3)))
216+
@test findfirst(equalto(1), (a=1, b=2)) == :a
217+
@test findlast(equalto(1), (a=1, b=2)) == :a
218+
@test findfirst(equalto(1), (a=1, b=1)) == :a
219+
@test findlast(equalto(1), (a=1, b=1)) == :b
220+
@test findfirst(equalto(1), ()) === nothing
221+
@test findlast(equalto(1), ()) === nothing
222+
@test findfirst(equalto(1), (a=2, b=3)) === nothing
223+
@test findlast(equalto(1), (a=2, b=3)) === nothing

test/tuple.jl

+21-5
Original file line numberDiff line numberDiff line change
@@ -365,9 +365,25 @@ end
365365
@test eltype(Tuple{Vararg{T}} where T<:Integer) >: Integer
366366
end
367367

368-
@testset "findall" begin
369-
@test @inferred findall(equalto(1), (1, 2)) == [1]
370-
@test @inferred findall(equalto(1), (1, 1)) == [1, 2]
371-
@test @inferred isempty(findall(equalto(1), ()))
372-
@test @inferred isempty(findall(equalto(1), (2, 3)))
368+
@testset "find" begin
369+
@test findall(equalto(1), (1, 2)) == [1]
370+
@test findall(equalto(1), (1, 1)) == [1, 2]
371+
@test isempty(findall(equalto(1), ()))
372+
@test isempty(findall(equalto(1), (2, 3)))
373+
374+
@test findfirst(equalto(1), (1, 2)) == 1
375+
@test findlast(equalto(1), (1, 2)) == 1
376+
@test findfirst(equalto(1), (1, 1)) == 1
377+
@test findlast(equalto(1), (1, 1)) == 2
378+
@test findfirst(equalto(1), ()) === nothing
379+
@test findlast(equalto(1), ()) === nothing
380+
@test findfirst(equalto(1), (2, 3)) === nothing
381+
@test findlast(equalto(1), (2, 3)) === nothing
382+
383+
@test findnext(equalto(1), (1, 2), 1) == 1
384+
@test findprev(equalto(1), (1, 2), 2) == 1
385+
@test findnext(equalto(1), (1, 1), 2) == 2
386+
@test findprev(equalto(1), (1, 1), 1) == 1
387+
@test findnext(equalto(1), (2, 3), 1) === nothing
388+
@test findprev(equalto(1), (2, 3), 2) === nothing
373389
end

0 commit comments

Comments
 (0)