Skip to content

Commit bd772d7

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 ef596e7 commit bd772d7

File tree

5 files changed

+155
-35
lines changed

5 files changed

+155
-35
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
find(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> find(x -> x >= 0, d)
18751938
"""
18761939
find(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
find(A)
18831943

test/arrayops.jl

+33-2
Original file line numberDiff line numberDiff line change
@@ -465,13 +465,44 @@ end
465465
@test find(isodd, A) == [CartesianIndex(1, 1), CartesianIndex(2, 1)]
466466
@test find(!iszero, A) == [CartesianIndex(1, 1), CartesianIndex(2, 1),
467467
CartesianIndex(1, 2), CartesianIndex(2, 2)]
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
468474
end
469475
@testset "find with general iterables" begin
470476
s = "julia"
471477
@test find(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 find(isascii, g) == Int[]
474-
@test find(!iszero, (i % 2 for i in 1:10)) == 1:2:9
486+
@test find(!isempty, g) == 1:3
487+
@test isempty(find(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 find(!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 find(!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

+9-4
Original file line numberDiff line numberDiff line change
@@ -761,8 +761,13 @@ end
761761
end
762762

763763
@testset "find" begin
764-
@test @inferred find(equalto(1), Dict(:a=>1, :b=>2)) == [:a]
765-
@test @inferred sort(find(equalto(1), Dict(:a=>1, :b=>1))) == [:a, :b]
766-
@test @inferred isempty(find(equalto(1), Dict()))
767-
@test @inferred isempty(find(equalto(1), Dict(:a=>2, :b=>3)))
764+
@test find(equalto(1), Dict(:a=>1, :b=>2)) == [:a]
765+
@test sort(find(equalto(1), Dict(:a=>1, :b=>1))) == [:a, :b]
766+
@test isempty(find(equalto(1), Dict()))
767+
@test isempty(find(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 find(equalto(1), (a=1, b=2)) == [:a]
213-
@test @inferred find(equalto(1), (a=1, b=1)) == [:a, :b]
214-
@test @inferred isempty(find(equalto(1), NamedTuple()))
215-
@test @inferred isempty(find(equalto(1), (a=2, b=3)))
212+
@test find(equalto(1), (a=1, b=2)) == [:a]
213+
@test find(equalto(1), (a=1, b=1)) == [:a, :b]
214+
@test isempty(find(equalto(1), NamedTuple()))
215+
@test isempty(find(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

+20-4
Original file line numberDiff line numberDiff line change
@@ -366,8 +366,24 @@ end
366366
end
367367

368368
@testset "find" begin
369-
@test @inferred find(equalto(1), (1, 2)) == [1]
370-
@test @inferred find(equalto(1), (1, 1)) == [1, 2]
371-
@test @inferred isempty(find(equalto(1), ()))
372-
@test @inferred isempty(find(equalto(1), (2, 3)))
369+
@test find(equalto(1), (1, 2)) == [1]
370+
@test find(equalto(1), (1, 1)) == [1, 2]
371+
@test isempty(find(equalto(1), ()))
372+
@test isempty(find(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)