diff --git a/NEWS.md b/NEWS.md index 6f2abf7d786a1..f899efbd3e66d 100644 --- a/NEWS.md +++ b/NEWS.md @@ -370,12 +370,12 @@ This section lists changes that do not have deprecation warnings. * `findn(x::AbstractArray)` has been deprecated in favor of `findall(!iszero, x)`, which now returns cartesian indices for multidimensional arrays (see below, [#25532]). - * `find` has been renamed to `findall`, and now returns the same type of indices - as `keys`/`pairs` for `AbstractArray`, `AbstractDict`, `AbstractString`, `Tuple` - and `NamedTuple` objects ([#24774], [#25545]). - In particular, this means that it returns `CartesianIndex` objects for matrices - and higher-dimensional arrays instead of linear indices as was previously the case. - Use `LinearIndices(a)[findall(f, a)]` to compute linear indices. + * `find` has been renamed to `findall`. `findall`, `findfirst`, `findlast`, `findnext` + now take and/or return the same type of indices as `keys`/`pairs` for `AbstractArray`, + `AbstractDict`, `AbstractString`, `Tuple` and `NamedTuple` objects ([#24774], [#25545]). + In particular, this means that they use `CartesianIndex` objects for matrices + and higher-dimensional arrays insted of linear indices as was previously the case. + Use `LinearIndices(a)[findall(f, a)]` and similar constructs to compute linear indices. * `AbstractSet` objects are now considered equal by `==` and `isequal` if all of their elements are equal ([#25368]). This has required changing the hashing algorithm diff --git a/base/array.jl b/base/array.jl index f5f779311a247..f621bc43ed224 100644 --- a/base/array.jl +++ b/base/array.jl @@ -1492,26 +1492,45 @@ cat(n::Integer, x::Integer...) = reshape([x...], (ntuple(x->1, n-1)..., length(x ## find ## +_pairs(A::Union{AbstractArray, AbstractDict, AbstractString, Tuple, NamedTuple}) = pairs(A) +_pairs(iter) = _pairs(IteratorSize(iter), iter) +_pairs(::Union{HasLength, HasShape}, iter) = zip(1:length(iter), iter) +_pairs(::Union{SizeUnknown, IsInfinite}, iter) = zip(Iterators.countfrom(1), iter) + """ findnext(A, i::Integer) Find the next linear index >= `i` of a `true` element of `A`, or `nothing` if not found. +Indices are of the same type as those returned by [`keys(A)`](@ref) +and [`pairs(A)`](@ref). + # Examples ```jldoctest +julia> A = [false, false, true, false] +4-element Array{Bool,1}: + false + false + true + false + +julia> findnext(A, 1) +3 + +julia> findnext(A, 4) == nothing +true + julia> A = [false false; true false] 2×2 Array{Bool,2}: false false true false -julia> findnext(A, 1) -2 - -julia> findnext(A, 3) +julia> findnext(A, CartesianIndex(1, 1)) +CartesianIndex(2, 1) ``` """ -function findnext(A, start::Integer) - l = endof(A) +function findnext(A, start) + l = last(keys(A)) i = start warned = false while i <= l @@ -1531,48 +1550,91 @@ end """ findfirst(A) -Return the linear index of the first `true` value in `A`. +Return the index or key of the first `true` value in `A`. Return `nothing` if no such value is found. To search for other kinds of values, pass a predicate as the first argument. +Indices or keys are of the same type as those returned by [`keys(A)`](@ref) +and [`pairs(A)`](@ref) for `AbstractArray`, `AbstractDict`, `AbstractString` +`Tuple` and `NamedTuple` objects, and are linear indices starting at `1` +for other iterables. + # Examples ```jldoctest +julia> A = [false, false, true, false] +4-element Array{Bool,1}: + false + false + true + false + +julia> findfirst(A) +3 + +julia> findfirst(falses(3)) == nothing +true + julia> A = [false false; true false] 2×2 Array{Bool,2}: false false true false julia> findfirst(A) -2 - -julia> findfirst(falses(3)) == nothing -true +CartesianIndex(2, 1) ``` """ -findfirst(A) = findnext(A, 1) +function findfirst(A) + warned = false + for (i, a) in _pairs(A) + if !warned && !(a isa Bool) + depwarn("In the future `findfirst` will only work on boolean collections. Use `findfirst(x->x!=0, A)` instead.", :findfirst) + warned = true + end + if a != 0 + return i + end + end + return nothing +end + +# Needed for bootstrap, and allows defining only an optimized findnext method +findfirst(A::Union{AbstractArray, AbstractString}) = findnext(A, first(keys(A))) """ - findnext(predicate::Function, A, i::Integer) + findnext(predicate::Function, A, i) -Find the next linear index >= `i` of an element of `A` for which `predicate` returns `true`, +Find the next index >= `i` of an element of `A` for which `predicate` returns `true`, or `nothing` if not found. +Indices are of the same type as those returned by [`keys(A)`](@ref) +and [`pairs(A)`](@ref). + # Examples ```jldoctest -julia> A = [1 4; 2 2] -2×2 Array{Int64,2}: - 1 4 - 2 2 +A = [1, 4, 2, 2] +4-element Array{Int64,1}: + 1 + 4 + 2 + 2 julia> findnext(isodd, A, 1) 1 julia> findnext(isodd, A, 2) == nothing true + +julia> A = [1 4; 2 2] +2×2 Array{Int64,2}: + 1 4 + 2 2 + +julia> findnext(isodd, A, CartesianIndex(1, 1)) +CartesianIndex(1, 1) ``` """ -function findnext(testf::Function, A, start::Integer) - l = endof(A) +function findnext(testf::Function, A, start) + l = last(keys(A)) i = start while i <= l if testf(A[i]) @@ -1586,15 +1648,22 @@ end """ findfirst(predicate::Function, A) -Return the linear index of the first element of `A` for which `predicate` returns `true`. +Return the index or key of the first element of `A` for which `predicate` returns `true`. Return `nothing` if there is no such element. +Indices or keys are of the same type as those returned by [`keys(A)`](@ref) +and [`pairs(A)`](@ref) for `AbstractArray`, `AbstractDict`, `AbstractString` +`Tuple` and `NamedTuple` objects, and are linear indices starting at `1` +for other iterables. + # Examples ```jldoctest -julia> A = [1 4; 2 2] -2×2 Array{Int64,2}: - 1 4 - 2 2 +julia> A = [1, 4, 2, 2] +4-element Array{Int64,1}: + 1 + 4 + 2 + 2 julia> findfirst(iseven, A) 2 @@ -1603,34 +1672,64 @@ julia> findfirst(x -> x>10, A) == nothing true julia> findfirst(equalto(4), A) -3 +2 + +julia> A = [1 4; 2 2] +2×2 Array{Int64,2}: + 1 4 + 2 2 + +julia> findfirst(iseven, A) +CartesianIndex(2, 1) ``` """ -findfirst(testf::Function, A) = findnext(testf, A, 1) +function findfirst(testf::Function, A) + for (i, a) in _pairs(A) + testf(a) && return i + end + return nothing +end + +# Needed for bootstrap, and allows defining only an optimized findnext method +findfirst(testf::Function, A::Union{AbstractArray, AbstractString}) = + findnext(testf, A, first(keys(A))) """ - findprev(A, i::Integer) + findprev(A, i) + +Find the previous index <= `i` of a `true` element of `A`, or `nothing` if not found. -Find the previous linear index <= `i` of a `true` element of `A`, or `nothing` if not found. +Indices are of the same type as those returned by [`keys(A)`](@ref) +and [`pairs(A)`](@ref). # Examples ```jldoctest +julia> A = [false, false, true, true] +4-element Array{Bool,1}: + false + false + true + true + +julia> findprev(A, 3) +3 + +julia> findprev(A, 1) == nothing +true + julia> A = [false false; true true] 2×2 Array{Bool,2}: false false true true -julia> findprev(A,2) -2 - -julia> findprev(A,1) == nothing -true +julia> findprev(A, CartesianIndex(2, 1)) +CartesianIndex(2, 1) ``` """ -function findprev(A, start::Integer) +function findprev(A, start) i = start warned = false - while i >= 1 + while i >= first(keys(A)) a = A[i] if !warned && !(a isa Bool) depwarn("In the future `findprev` will only work on boolean collections. Use `findprev(x->x!=0, A, start)` instead.", :findprev) @@ -1645,50 +1744,93 @@ end """ findlast(A) -Return the linear index of the last `true` value in `A`. +Return the index or key of the last `true` value in `A`. Return `nothing` if there is no `true` value in `A`. +Indices or keys are of the same type as those returned by [`keys(A)`](@ref) +and [`pairs(A)`](@ref) for `AbstractArray`, `AbstractDict`, `AbstractString` +`Tuple` and `NamedTuple` objects, and are linear indices starting at `1` +for other iterables. + # Examples ```jldoctest -julia> A = [true false; true false] -2×2 Array{Bool,2}: - true false - true false +julia> A = [true, false, true, false] +4-element Array{Bool,1}: + true + false + true + false julia> findlast(A) -2 +3 julia> A = falses(2,2); julia> findlast(A) == nothing true + +julia> A = [true false; true false] +2×2 Array{Bool,2}: + true false + true false + +julia> findlast(A) +CartesianIndex(2, 1) ``` """ -findlast(A) = findprev(A, endof(A)) +function findlast(A) + warned = false + for (i, a) in Iterators.reverse(_pairs(A)) + if !warned && !(a isa Bool) + depwarn("In the future `findlast` will only work on boolean collections. Use `findlast(x->x!=0, A)` instead.", :findlast) + warned = true + end + if a != 0 + return i + end + end + return nothing +end + +# Needed for bootstrap, and allows defining only an optimized findprev method +findlast(A::Union{AbstractArray, AbstractString}) = findprev(A, last(keys(A))) """ - findprev(predicate::Function, A, i::Integer) + findprev(predicate::Function, A, i) -Find the previous linear index <= `i` of an element of `A` for which `predicate` returns `true`, or +Find the previous index <= `i` of an element of `A` for which `predicate` returns `true`, or `nothing` if not found. +Indices are of the same type as those returned by [`keys(A)`](@ref) +and [`pairs(A)`](@ref). + # Examples ```jldoctest -julia> A = [4 6; 1 2] -2×2 Array{Int64,2}: - 4 6 - 1 2 +julia> A = [4, 6, 1, 2] +4-element Array{Int64,1}: + 4 + 6 + 1 + 2 julia> findprev(isodd, A, 1) == nothing true julia> findprev(isodd, A, 3) -2 +3 + +julia> A = [4 6; 1 2] +2×2 Array{Int64,2}: + 4 6 + 1 2 + +julia> findprev(isodd, A, CartesianIndex(1, 2)) +CartesianIndex(2, 1) ``` """ -function findprev(testf::Function, A, start::Integer) +function findprev(testf::Function, A, start) i = start - while i >= 1 + while i >= first(keys(A)) testf(A[i]) && return i i = prevind(A, i) end @@ -1698,24 +1840,48 @@ end """ findlast(predicate::Function, A) -Return the linear index of the last element of `A` for which `predicate` returns `true`. +Return the index or key of the last element of `A` for which `predicate` returns `true`. Return `nothing` if there is no such element. +Indices or keys are of the same type as those returned by [`keys(A)`](@ref) +and [`pairs(A)`](@ref) for `AbstractArray`, `AbstractDict`, `AbstractString` +`Tuple` and `NamedTuple` objects, and are linear indices starting at `1` +for other iterables. + # Examples ```jldoctest +julia> A = [1, 2, 3, 4] +4-element Array{Int64,1}: + 1 + 2 + 3 + 4 + +julia> findlast(isodd, A) +3 + +julia> findlast(x -> x > 5, A) == nothing +true + julia> A = [1 2; 3 4] 2×2 Array{Int64,2}: 1 2 3 4 julia> findlast(isodd, A) -2 - -julia> findlast(x -> x > 5, A) == nothing -true +CartesianIndex(2, 1) ``` """ -findlast(testf::Function, A) = findprev(testf, A, endof(A)) +function findlast(testf::Function, A) + for (i, a) in Iterators.reverse(_pairs(A)) + testf(a) && return i + end + return nothing +end + +# Needed for bootstrap, and allows defining only an optimized findprev method +findlast(testf::Function, A::Union{AbstractArray, AbstractString}) = + findprev(testf, A, last(keys(A))) """ findall(f::Function, A) @@ -1772,9 +1938,6 @@ julia> findall(x -> x >= 0, d) """ findall(testf::Function, A) = collect(first(p) for p in _pairs(A) if testf(last(p))) -_pairs(A::Union{AbstractArray, AbstractDict, AbstractString, Tuple, NamedTuple}) = pairs(A) -_pairs(iter) = zip(OneTo(typemax(Int)), iter) # safe for objects that don't implement length - """ findall(A) diff --git a/base/deprecated.jl b/base/deprecated.jl index bc7776350873c..a65148a1b162a 100644 --- a/base/deprecated.jl +++ b/base/deprecated.jl @@ -1184,6 +1184,9 @@ end @deprecate findfirst(A, v) findfirst(equalto(v), A) @deprecate findprev(A, v, i::Integer) findprev(equalto(v), A, i) @deprecate findlast(A, v) findlast(equalto(v), A) +# to fix ambiguities introduced by deprecations +findnext(pred::Function, A, i::Integer) = invoke(findnext, Tuple{Function, Any, Any}, pred, A, i) +findprev(pred::Function, A, i::Integer) = invoke(findprev, Tuple{Function, Any, Any}, pred, A, i) # also remove deprecation warnings in find* functions in array.jl, sparse/sparsematrix.jl, # and sparse/sparsevector.jl. diff --git a/base/multidimensional.jl b/base/multidimensional.jl index d49aa104e21a4..555c9d64695c0 100644 --- a/base/multidimensional.jl +++ b/base/multidimensional.jl @@ -142,11 +142,15 @@ module IteratorsMD return h end - # nextind with CartesianIndex + # nextind and prevind with CartesianIndex function Base.nextind(a::AbstractArray{<:Any,N}, i::CartesianIndex{N}) where {N} _, ni = next(CartesianIndices(axes(a)), i) return ni end + function Base.prevind(a::AbstractArray{<:Any,N}, i::CartesianIndex{N}) where {N} + _, ni = next(Iterators.reverse(CartesianIndices(axes(a))), i) + return ni + end # Iteration over the elements of CartesianIndex cannot be supported until its length can be inferred, # see #23719 diff --git a/base/strings/basic.jl b/base/strings/basic.jl index 5b9f00874a5a3..8ed6dab404919 100644 --- a/base/strings/basic.jl +++ b/base/strings/basic.jl @@ -480,6 +480,8 @@ end keys(s::AbstractString) = EachStringIndex(s) length(e::EachStringIndex) = length(e.s) +first(::EachStringIndex) = 1 +last(e::EachStringIndex) = endof(e.s) start(e::EachStringIndex) = start(e.s) next(e::EachStringIndex, state) = (state, nextind(e.s, state)) done(e::EachStringIndex, state) = done(e.s, state) diff --git a/stdlib/SparseArrays/test/sparse.jl b/stdlib/SparseArrays/test/sparse.jl index 299b7dfe85e94..446628cb5d0ed 100644 --- a/stdlib/SparseArrays/test/sparse.jl +++ b/stdlib/SparseArrays/test/sparse.jl @@ -2183,7 +2183,7 @@ end 1 0 1 1 0] y_sp = sparse(y) - for i=1:length(y) + for i in keys(y) @test findnext(!iszero, y,i) == findnext(!iszero, y_sp,i) @test findprev(!iszero, y,i) == findprev(!iszero, y_sp,i) end @@ -2191,7 +2191,7 @@ end z_sp = sparsevec(Dict(1=>1, 5=>1, 8=>0, 10=>1)) z = collect(z_sp) - for i=1:length(z) + for i in keys(z) @test findnext(!iszero, z,i) == findnext(!iszero, z_sp,i) @test findprev(!iszero, z,i) == findprev(!iszero, z_sp,i) end diff --git a/test/abstractarray.jl b/test/abstractarray.jl index 820c2ae81ad5d..24416c9245bed 100644 --- a/test/abstractarray.jl +++ b/test/abstractarray.jl @@ -817,9 +817,11 @@ for A in (rand(2), rand(2,3)) @test Array(values(A)) == A end -# nextind +# nextind and prevind @test nextind(zeros(4), 2) == 3 @test nextind(zeros(2,3), CartesianIndex(2,1)) == CartesianIndex(1, 2) +@test prevind(zeros(4), 2) == 1 +@test prevind(zeros(2,3), CartesianIndex(2,1)) == CartesianIndex(1, 1) @testset "ImageCore #40" begin Base.convert(::Type{Array{T,n}}, a::Array{T,n}) where {T<:Number,n} = a diff --git a/test/arrayops.jl b/test/arrayops.jl index 7157ca70e3360..ce1e3a7082894 100644 --- a/test/arrayops.jl +++ b/test/arrayops.jl @@ -460,18 +460,49 @@ end @test findnext(equalto(0x00), [0x00, 0x01, 0x00], 2) == 3 @test findprev(equalto(0x00), [0x00, 0x01, 0x00], 2) == 1 end -@testset "findall with Matrix" begin +@testset "find with Matrix" begin A = [1 2 0; 3 4 0] @test findall(isodd, A) == [CartesianIndex(1, 1), CartesianIndex(2, 1)] @test findall(!iszero, A) == [CartesianIndex(1, 1), CartesianIndex(2, 1), CartesianIndex(1, 2), CartesianIndex(2, 2)] -end -@testset "findall with general iterables" begin + @test findfirst(isodd, A) == CartesianIndex(1, 1) + @test findlast(isodd, A) == CartesianIndex(2, 1) + @test findnext(isodd, A, CartesianIndex(1, 1)) == CartesianIndex(1, 1) + @test findprev(isodd, A, CartesianIndex(2, 1)) == CartesianIndex(2, 1) + @test findnext(isodd, A, CartesianIndex(1, 2)) === nothing + @test findprev(iseven, A, CartesianIndex(2, 1)) === nothing +end +@testset "find with general iterables" begin s = "julia" @test findall(c -> c == 'l', s) == [3] + @test findfirst(c -> c == 'l', s) == 3 + @test findlast(c -> c == 'l', s) == 3 + @test findnext(c -> c == 'l', s, 1) == 3 + @test findprev(c -> c == 'l', s, 5) == 3 + @test findnext(c -> c == 'l', s, 4) === nothing + @test findprev(c -> c == 'l', s, 2) === nothing + g = Base.Unicode.graphemes("日本語") - @test findall(isascii, g) == Int[] - @test findall(!iszero, (i % 2 for i in 1:10)) == 1:2:9 + @test findall(!isempty, g) == 1:3 + @test isempty(findall(isascii, g)) + @test findfirst(!isempty, g) == 1 + @test findfirst(isascii, g) === nothing + # Check that the last index isn't assumed to be typemax(Int) + @test_throws MethodError findlast(!iszero, g) + + g2 = (i % 2 for i in 1:10) + @test findall(!iszero, g2) == 1:2:9 + @test findfirst(!iszero, g2) == 1 + @test findlast(!iszero, g2) == 9 + @test findfirst(equalto(2), g2) === nothing + @test findlast(equalto(2), g2) === nothing + + g3 = (i % 2 for i in 1:10, j in 1:2) + @test findall(!iszero, g3) == 1:2:19 + @test findfirst(!iszero, g3) == 1 + @test findlast(!iszero, g3) == 19 + @test findfirst(equalto(2), g3) === nothing + @test findlast(equalto(2), g3) === nothing end @testset "findmin findmax indmin indmax" begin diff --git a/test/dict.jl b/test/dict.jl index 45d60f6210eb5..5b9c3da1935ba 100644 --- a/test/dict.jl +++ b/test/dict.jl @@ -760,9 +760,14 @@ end @test map(string, keys(d)) == Set(["1","3"]) end -@testset "findall" begin - @test @inferred findall(equalto(1), Dict(:a=>1, :b=>2)) == [:a] - @test @inferred sort(findall(equalto(1), Dict(:a=>1, :b=>1))) == [:a, :b] - @test @inferred isempty(findall(equalto(1), Dict())) - @test @inferred isempty(findall(equalto(1), Dict(:a=>2, :b=>3))) +@testset "find" begin + @test findall(equalto(1), Dict(:a=>1, :b=>2)) == [:a] + @test sort(findall(equalto(1), Dict(:a=>1, :b=>1))) == [:a, :b] + @test isempty(findall(equalto(1), Dict())) + @test isempty(findall(equalto(1), Dict(:a=>2, :b=>3))) + + @test findfirst(equalto(1), Dict(:a=>1, :b=>2)) == :a + @test findfirst(equalto(1), Dict(:a=>1, :b=>1, :c=>3)) in (:a, :b) + @test findfirst(equalto(1), Dict()) === nothing + @test findfirst(equalto(1), Dict(:a=>2, :b=>3)) === nothing end \ No newline at end of file diff --git a/test/namedtuple.jl b/test/namedtuple.jl index c20bc717f4615..85d8bd1816e15 100644 --- a/test/namedtuple.jl +++ b/test/namedtuple.jl @@ -209,7 +209,15 @@ abstr_nt_22194_3() @test typeof(Base.structdiff(NamedTuple{(:a, :b), Tuple{Int32, Union{Int32, Nothing}}}((1, Int32(2))), (a=0,))) === NamedTuple{(:b,), Tuple{Union{Int32, Nothing}}} -@test @inferred findall(equalto(1), (a=1, b=2)) == [:a] -@test @inferred findall(equalto(1), (a=1, b=1)) == [:a, :b] -@test @inferred isempty(findall(equalto(1), NamedTuple())) -@test @inferred isempty(findall(equalto(1), (a=2, b=3))) \ No newline at end of file +@test findall(equalto(1), (a=1, b=2)) == [:a] +@test findall(equalto(1), (a=1, b=1)) == [:a, :b] +@test isempty(findall(equalto(1), NamedTuple())) +@test isempty(findall(equalto(1), (a=2, b=3))) +@test findfirst(equalto(1), (a=1, b=2)) == :a +@test findlast(equalto(1), (a=1, b=2)) == :a +@test findfirst(equalto(1), (a=1, b=1)) == :a +@test findlast(equalto(1), (a=1, b=1)) == :b +@test findfirst(equalto(1), ()) === nothing +@test findlast(equalto(1), ()) === nothing +@test findfirst(equalto(1), (a=2, b=3)) === nothing +@test findlast(equalto(1), (a=2, b=3)) === nothing diff --git a/test/strings/basic.jl b/test/strings/basic.jl index 29ea38b3664f2..dcf007e6860ab 100644 --- a/test/strings/basic.jl +++ b/test/strings/basic.jl @@ -233,6 +233,9 @@ end @test_throws StringIndexError GenericString("∀∃")[Int8(2)] @test_throws BoundsError GenericString("∀∃")[UInt16(10)] + @test first(eachindex("foobar")) === 1 + @test first(eachindex("")) === 1 + @test last(eachindex("foobar")) === endof("foobar") @test done(eachindex("foobar"),7) @test eltype(Base.EachStringIndex) == Int @test map(uppercase, "foó") == "FOÓ" diff --git a/test/tuple.jl b/test/tuple.jl index 0e74b267b5b85..713592d2e1980 100644 --- a/test/tuple.jl +++ b/test/tuple.jl @@ -365,9 +365,25 @@ end @test eltype(Tuple{Vararg{T}} where T<:Integer) >: Integer end -@testset "findall" begin - @test @inferred findall(equalto(1), (1, 2)) == [1] - @test @inferred findall(equalto(1), (1, 1)) == [1, 2] - @test @inferred isempty(findall(equalto(1), ())) - @test @inferred isempty(findall(equalto(1), (2, 3))) +@testset "find" begin + @test findall(equalto(1), (1, 2)) == [1] + @test findall(equalto(1), (1, 1)) == [1, 2] + @test isempty(findall(equalto(1), ())) + @test isempty(findall(equalto(1), (2, 3))) + + @test findfirst(equalto(1), (1, 2)) == 1 + @test findlast(equalto(1), (1, 2)) == 1 + @test findfirst(equalto(1), (1, 1)) == 1 + @test findlast(equalto(1), (1, 1)) == 2 + @test findfirst(equalto(1), ()) === nothing + @test findlast(equalto(1), ()) === nothing + @test findfirst(equalto(1), (2, 3)) === nothing + @test findlast(equalto(1), (2, 3)) === nothing + + @test findnext(equalto(1), (1, 2), 1) == 1 + @test findprev(equalto(1), (1, 2), 2) == 1 + @test findnext(equalto(1), (1, 1), 2) == 2 + @test findprev(equalto(1), (1, 1), 1) == 1 + @test findnext(equalto(1), (2, 3), 1) === nothing + @test findprev(equalto(1), (2, 3), 2) === nothing end \ No newline at end of file