From 130ad51b994d3e3a01990eaaab0b1ca1dd92d107 Mon Sep 17 00:00:00 2001 From: Milan Bouchet-Valat Date: Sat, 3 Feb 2018 15:49:44 +0100 Subject: [PATCH 1/2] Add occursin and find* methods replacing search and findin Add OccursIn predicate which replaces findin and all specific findfirst/findnext/findlast/findprev methods for specific types. These all call search or findin, but return nothing instead of 0 where appropriate. Note that there is an inconsistency with generic find* methods which already existed in Julia 0.6 and which continue to return 0: separate Compat.find* methods will have to be introduced for them to avoid conflicts with Base. Also fix in(::CartesianIndex, ::CartesianIndices) bug uncovered by importing in. --- README.md | 6 ++++++ src/Compat.jl | 56 +++++++++++++++++++++++++++++++++++++++++++++++- test/runtests.jl | 29 +++++++++++++++++++++++++ 3 files changed, 90 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 6dc623fb5..848bdf37c 100644 --- a/README.md +++ b/README.md @@ -327,6 +327,11 @@ Currently, the `@compat` macro supports the following syntaxes: * `find` is now `findall` ([#25545]). +* `search` is now `findfirst`/`findnext` and `rsearch` is now `findlast`/`findprev`, + sometimes combined with `equalto` or `occursin` ([#24673]). + +* `findin(a, b)` is now `findall(occursin(b), a)` ([#24673]). + * `indmin` and `indmax` are now `argmin` and `argmax`, respectively ([#25654]). * `isabstract` and `isleaftype` are now `isabstracttype` and `isconcretetype`, respectively @@ -540,3 +545,4 @@ includes this fix. Find the minimum version from there. [#25705]: https://github.com/JuliaLang/julia/issues/25705 [#25780]: https://github.com/JuliaLang/julia/issues/25780 [#24182]: https://github.com/JuliaLang/julia/issues/24182 +[#24673]: https://github.com/JuliaLang/julia/issues/24673 \ No newline at end of file diff --git a/src/Compat.jl b/src/Compat.jl index 28a394b41..b71d97460 100644 --- a/src/Compat.jl +++ b/src/Compat.jl @@ -1261,7 +1261,7 @@ end @static if VERSION < v"0.7.0-DEV.3025" import Base: convert, ndims, getindex, size, length, eltype, - start, next, done, first, last + start, next, done, first, last, in, tail export CartesianIndices, LinearIndices struct CartesianIndices{N,R<:NTuple{N,AbstractUnitRange{Int}}} <: AbstractArray{CartesianIndex{N},N} @@ -1433,6 +1433,7 @@ else @eval const $(Symbol("@error")) = Base.$(Symbol("@error")) end +# 0.7.0-DEV.3415 if !isdefined(Base, :findall) const findall = find export findall @@ -1557,6 +1558,59 @@ end export objectid end +@static if VERSION < v"0.7.0-DEV.3272" + import Base: in, findfirst, findnext, findlast, findprev + + struct OccursIn{T} <: Function + x::T + + OccursIn(x::T) where {T} = new{T}(x) + end + (f::OccursIn)(y) = y in f.x + const occursin = OccursIn + export occursin + + zero2nothing(x) = x == 0 ? nothing : x + + findnext(r::Regex, s::AbstractString, idx::Integer) = search(s, r, idx) + findfirst(r::Regex, s::AbstractString) = search(s, r) + findnext(c::EqualTo{Char}, s::AbstractString, i::Integer) = zero2nothing(search(s, c.x, i)) + findfirst(c::EqualTo{Char}, s::AbstractString) = zero2nothing(search(s, c.x)) + findnext(b::EqualTo{<:Union{Int8,UInt8}}, a::Vector{<:Union{Int8,UInt8}}, i::Integer) = + zero2nothing(search(a, b.x, i)) + findfirst(b::EqualTo{<:Union{Int8,UInt8}}, a::Vector{<:Union{Int8,UInt8}}) = + zero2nothing(search(a, b.x)) + + findnext(c::OccursIn{<:Union{Tuple{Vararg{Char}},AbstractVector{Char},Set{Char}}}, + s::AbstractString, i::Integer) = + zero2nothing(search(s, c.x, i)) + findfirst(c::OccursIn{<:Union{Tuple{Vararg{Char}},AbstractVector{Char},Set{Char}}}, + s::AbstractString) = + zero2nothing(search(s, c.x)) + findnext(t::AbstractString, s::AbstractString, i::Integer) = search(s, t, i) + findfirst(t::AbstractString, s::AbstractString) = search(s, t) + + findfirst(delim::EqualTo{UInt8}, buf::IOBuffer) = zero2nothing(search(buf, delim.x)) + + findprev(c::EqualTo{Char}, s::AbstractString, i::Integer) = zero2nothing(rsearch(s, c.x, i)) + findlast(c::EqualTo{Char}, s::AbstractString) = zero2nothing(rsearch(s, c.x)) + findprev(b::EqualTo{<:Union{Int8,UInt8}}, a::Vector{<:Union{Int8,UInt8}}, i::Integer) = + zero2nothing(rsearch(a, b.x, i)) + findlast(b::EqualTo{<:Union{Int8,UInt8}}, a::Vector{<:Union{Int8,UInt8}}) = + zero2nothing(rsearch(a, b.x)) + + findprev(c::OccursIn{<:Union{Tuple{Vararg{Char}},AbstractVector{Char},Set{Char}}}, + s::AbstractString, i::Integer) = zero2nothing(rsearch(s, c.x, i)) + findlast(c::OccursIn{<:Union{Tuple{Vararg{Char}},AbstractVector{Char},Set{Char}}}, + s::AbstractString) = zero2nothing(rsearch(s, c.x)) + findprev(t::AbstractString, s::AbstractString, i::Integer) = rsearch(s, t, i) + findlast(t::AbstractString, s::AbstractString) = rsearch(s, t) + + findall(b::OccursIn, a) = findin(a, b.x) + # To fix ambiguity + findall(b::OccursIn, a::Number) = a in b.x ? [1] : Vector{Int}() +end + include("deprecated.jl") end # module Compat diff --git a/test/runtests.jl b/test/runtests.jl index ef9407992..c46cea31a 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1206,6 +1206,7 @@ let c = CartesianIndices(1:3, 1:2), l = LinearIndices(1:3, 1:2) @test c[1:6] == vec(c) @test l == l[c] == map(i -> l[i], c) @test l[vec(c)] == collect(1:6) + @test CartesianIndex(1, 1) in CartesianIndices((3, 4)) end if !isdefined(Base, Symbol("@info")) @@ -1340,4 +1341,32 @@ let x = y = 1 @test objectid(x) == objectid(y) end +# 0.7.0-DEV.3415 +for (f1, f2, i) in ((findfirst, findnext, 1), (findlast, findprev, 2)) + # Generic methods + @test f1(equalto(0), [1, 0]) == f2(equalto(0), [1, 0], i) == 2 + @test f1(occursin([0, 2]), [1, 0]) == f2(occursin([0, 2]), [1, 0], i) == 2 + + # Specific methods + @test f2(equalto('a'), "ba", i) == f1(equalto('a'), "ba") == 2 + for S in (Int8, UInt8), T in (Int8, UInt8) + # Bug in Julia 0.6 + f1 === findlast && VERSION < v"0.7.0-DEV.3272" && continue + @test f2(equalto(S(1)), T[0, 1], i) == f1(equalto(S(1)), T[0, 1]) == 2 + @test f2(equalto(S(9)), T[0, 1], i) == f1(equalto(S(9)), T[0, 1]) == nothing + end + for chars in (['a', 'z'], Set(['a', 'z']), ('a', 'z')) + @test f2(occursin(chars), "ba", i) == f1(occursin(chars), "ba") == 2 + @test f2(occursin(chars), "bx", i) == f1(occursin(chars), "bx") == nothing + end + @test f2("a", "ba", i) == f1("a", "ba") == 2:2 + @test f2("z", "ba", i) == f1("z", "ba") == 0:-1 +end +@test findnext(r"a", "ba", 1) == findfirst(r"a", "ba") == 2:2 +@test findnext(r"z", "ba", 1) == findfirst(r"z", "ba") == 0:-1 +@test findfirst(equalto(UInt8(0)), IOBuffer(UInt8[1, 0])) == 2 +@test findfirst(equalto(UInt8(9)), IOBuffer(UInt8[1, 0])) == nothing +@test findall([true, false, true]) == [1, 3] +@test findall(occursin([1, 2]), [1]) == [1] + nothing From b94ae1d5a0101681deef2035875a9835c401c8f3 Mon Sep 17 00:00:00 2001 From: Milan Bouchet-Valat Date: Sat, 3 Feb 2018 19:28:15 +0100 Subject: [PATCH 2/2] Add unexported find* functions returning nothing Change methods added to Base functions to return 0 on 0.6, which matches what already happens without the optimized methods. --- README.md | 3 ++ src/Compat.jl | 77 +++++++++++++++++++++++++++--------------------- test/runtests.jl | 27 +++++++++++++---- 3 files changed, 67 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index 848bdf37c..59fd9df69 100644 --- a/README.md +++ b/README.md @@ -330,6 +330,9 @@ Currently, the `@compat` macro supports the following syntaxes: * `search` is now `findfirst`/`findnext` and `rsearch` is now `findlast`/`findprev`, sometimes combined with `equalto` or `occursin` ([#24673]). +* `Compat.findfirst`, `Compat.findnext`, `Compat.findlast` and `Compat.findprev`, + return `nothing` when no match is found (rather than `0`) as on Julia 0.7 ([#24673]). + * `findin(a, b)` is now `findall(occursin(b), a)` ([#24673]). * `indmin` and `indmax` are now `argmin` and `argmax`, respectively ([#25654]). diff --git a/src/Compat.jl b/src/Compat.jl index b71d97460..e7bfb388a 100644 --- a/src/Compat.jl +++ b/src/Compat.jl @@ -1558,9 +1558,12 @@ end export objectid end -@static if VERSION < v"0.7.0-DEV.3272" - import Base: in, findfirst, findnext, findlast, findprev - +@static if VERSION >= v"0.7.0-DEV.3272" + findnext(xs...) = Base.findnext(xs...) + findfirst(xs...) = Base.findfirst(xs...) + findprev(xs...) = Base.findprev(xs...) + findlast(xs...) = Base.findlast(xs...) +else struct OccursIn{T} <: Function x::T @@ -1570,41 +1573,47 @@ end const occursin = OccursIn export occursin - zero2nothing(x) = x == 0 ? nothing : x + zero2nothing(x::Integer) = x == 0 ? nothing : x + zero2nothing(x) = x + + findnext(xs...) = zero2nothing(Base.findnext(xs...)) + findfirst(xs...) = zero2nothing(Base.findfirst(xs...)) + findprev(xs...) = zero2nothing(Base.findprev(xs...)) + findlast(xs...) = zero2nothing(Base.findlast(xs...)) - findnext(r::Regex, s::AbstractString, idx::Integer) = search(s, r, idx) - findfirst(r::Regex, s::AbstractString) = search(s, r) - findnext(c::EqualTo{Char}, s::AbstractString, i::Integer) = zero2nothing(search(s, c.x, i)) - findfirst(c::EqualTo{Char}, s::AbstractString) = zero2nothing(search(s, c.x)) - findnext(b::EqualTo{<:Union{Int8,UInt8}}, a::Vector{<:Union{Int8,UInt8}}, i::Integer) = - zero2nothing(search(a, b.x, i)) - findfirst(b::EqualTo{<:Union{Int8,UInt8}}, a::Vector{<:Union{Int8,UInt8}}) = - zero2nothing(search(a, b.x)) + Base.findnext(r::Regex, s::AbstractString, idx::Integer) = search(s, r, idx) + Base.findfirst(r::Regex, s::AbstractString) = search(s, r) + Base.findnext(c::EqualTo{Char}, s::AbstractString, i::Integer) = search(s, c.x, i) + Base.findfirst(c::EqualTo{Char}, s::AbstractString) = search(s, c.x) + Base.findnext(b::EqualTo{<:Union{Int8,UInt8}}, a::Vector{<:Union{Int8,UInt8}}, i::Integer) = + search(a, b.x, i) + Base.findfirst(b::EqualTo{<:Union{Int8,UInt8}}, a::Vector{<:Union{Int8,UInt8}}) = + search(a, b.x) - findnext(c::OccursIn{<:Union{Tuple{Vararg{Char}},AbstractVector{Char},Set{Char}}}, + Base.findnext(c::OccursIn{<:Union{Tuple{Vararg{Char}},AbstractVector{Char},Set{Char}}}, s::AbstractString, i::Integer) = - zero2nothing(search(s, c.x, i)) - findfirst(c::OccursIn{<:Union{Tuple{Vararg{Char}},AbstractVector{Char},Set{Char}}}, + search(s, c.x, i) + Base.findfirst(c::OccursIn{<:Union{Tuple{Vararg{Char}},AbstractVector{Char},Set{Char}}}, s::AbstractString) = - zero2nothing(search(s, c.x)) - findnext(t::AbstractString, s::AbstractString, i::Integer) = search(s, t, i) - findfirst(t::AbstractString, s::AbstractString) = search(s, t) - - findfirst(delim::EqualTo{UInt8}, buf::IOBuffer) = zero2nothing(search(buf, delim.x)) - - findprev(c::EqualTo{Char}, s::AbstractString, i::Integer) = zero2nothing(rsearch(s, c.x, i)) - findlast(c::EqualTo{Char}, s::AbstractString) = zero2nothing(rsearch(s, c.x)) - findprev(b::EqualTo{<:Union{Int8,UInt8}}, a::Vector{<:Union{Int8,UInt8}}, i::Integer) = - zero2nothing(rsearch(a, b.x, i)) - findlast(b::EqualTo{<:Union{Int8,UInt8}}, a::Vector{<:Union{Int8,UInt8}}) = - zero2nothing(rsearch(a, b.x)) - - findprev(c::OccursIn{<:Union{Tuple{Vararg{Char}},AbstractVector{Char},Set{Char}}}, - s::AbstractString, i::Integer) = zero2nothing(rsearch(s, c.x, i)) - findlast(c::OccursIn{<:Union{Tuple{Vararg{Char}},AbstractVector{Char},Set{Char}}}, - s::AbstractString) = zero2nothing(rsearch(s, c.x)) - findprev(t::AbstractString, s::AbstractString, i::Integer) = rsearch(s, t, i) - findlast(t::AbstractString, s::AbstractString) = rsearch(s, t) + search(s, c.x) + Base.findnext(t::AbstractString, s::AbstractString, i::Integer) = search(s, t, i) + Base.findfirst(t::AbstractString, s::AbstractString) = search(s, t) + + Base.findfirst(delim::EqualTo{UInt8}, buf::IOBuffer) = search(buf, delim.x) + + Base.findprev(c::EqualTo{Char}, s::AbstractString, i::Integer) = rsearch(s, c.x, i) + Base.findlast(c::EqualTo{Char}, s::AbstractString) = rsearch(s, c.x) + Base.findprev(b::EqualTo{<:Union{Int8,UInt8}}, a::Vector{<:Union{Int8,UInt8}}, i::Integer) = + rsearch(a, b.x, i) + Base.findlast(b::EqualTo{<:Union{Int8,UInt8}}, a::Vector{<:Union{Int8,UInt8}}) = + rsearch(a, b.x) + + Base.findprev(c::OccursIn{<:Union{Tuple{Vararg{Char}},AbstractVector{Char},Set{Char}}}, + s::AbstractString, i::Integer) = rsearch(s, c.x, i) + Base.findlast(c::OccursIn{<:Union{Tuple{Vararg{Char}},AbstractVector{Char},Set{Char}}}, + s::AbstractString) = rsearch(s, c.x) + Base.findprev(t::AbstractString, s::AbstractString, i::Integer) = rsearch(s, t, i) + Base.findlast(t::AbstractString, s::AbstractString) = rsearch(s, t) findall(b::OccursIn, a) = findin(a, b.x) # To fix ambiguity diff --git a/test/runtests.jl b/test/runtests.jl index c46cea31a..12220ff07 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1342,16 +1342,21 @@ let x = y = 1 end # 0.7.0-DEV.3415 -for (f1, f2, i) in ((findfirst, findnext, 1), (findlast, findprev, 2)) +for (f1, f2, i) in ((Compat.findfirst, Compat.findnext, 1), + (Compat.findlast, Compat.findprev, 2)) # Generic methods @test f1(equalto(0), [1, 0]) == f2(equalto(0), [1, 0], i) == 2 + @test f1(equalto(9), [1, 0]) == f2(equalto(9), [1, 0], i) == nothing @test f1(occursin([0, 2]), [1, 0]) == f2(occursin([0, 2]), [1, 0], i) == 2 + @test f1(occursin([0, 2]), [1, 9]) == f2(occursin([0, 2]), [1, 9], i) == nothing + @test f1([true, false]) == f2([true, false], i) == 1 + @test f1([false, false]) == f2([false, false], i) == nothing # Specific methods @test f2(equalto('a'), "ba", i) == f1(equalto('a'), "ba") == 2 for S in (Int8, UInt8), T in (Int8, UInt8) # Bug in Julia 0.6 - f1 === findlast && VERSION < v"0.7.0-DEV.3272" && continue + f1 === Compat.findlast && VERSION < v"0.7.0-DEV.3272" && continue @test f2(equalto(S(1)), T[0, 1], i) == f1(equalto(S(1)), T[0, 1]) == 2 @test f2(equalto(S(9)), T[0, 1], i) == f1(equalto(S(9)), T[0, 1]) == nothing end @@ -1359,13 +1364,23 @@ for (f1, f2, i) in ((findfirst, findnext, 1), (findlast, findprev, 2)) @test f2(occursin(chars), "ba", i) == f1(occursin(chars), "ba") == 2 @test f2(occursin(chars), "bx", i) == f1(occursin(chars), "bx") == nothing end +end +for (f1, f2, i) in ((findfirst, findnext, 1), + (findlast, findprev, 2), + (Compat.findfirst, Compat.findnext, 1), + (Compat.findlast, Compat.findprev, 2)) @test f2("a", "ba", i) == f1("a", "ba") == 2:2 @test f2("z", "ba", i) == f1("z", "ba") == 0:-1 end -@test findnext(r"a", "ba", 1) == findfirst(r"a", "ba") == 2:2 -@test findnext(r"z", "ba", 1) == findfirst(r"z", "ba") == 0:-1 -@test findfirst(equalto(UInt8(0)), IOBuffer(UInt8[1, 0])) == 2 -@test findfirst(equalto(UInt8(9)), IOBuffer(UInt8[1, 0])) == nothing +for (f1, f2, i) in ((findfirst, findnext, 1), + (Compat.findfirst, Compat.findnext, 1)) + @test f2(r"a", "ba", 1) == f1(r"a", "ba") == 2:2 + @test f2(r"z", "ba", 1) == f1(r"z", "ba") == 0:-1 +end + +@test Compat.findfirst(equalto(UInt8(0)), IOBuffer(UInt8[1, 0])) == 2 +@test Compat.findfirst(equalto(UInt8(9)), IOBuffer(UInt8[1, 0])) == nothing + @test findall([true, false, true]) == [1, 3] @test findall(occursin([1, 2]), [1]) == [1]