diff --git a/NEWS.md b/NEWS.md index cdf940755907f..d7e1ceeab61d7 100644 --- a/NEWS.md +++ b/NEWS.md @@ -261,6 +261,9 @@ This section lists changes that do not have deprecation warnings. Library improvements -------------------- + * The function `thisind(s::AbstractString, i::Integer)` returns the largest valid index + less or equal than `i` in the string `s` or `0` if no such index exists ([#24414]). + * `Irrational` is now a subtype of `AbstractIrrational` ([#24245]). * The function `chop` now accepts two arguments `head` and `tail` allowing to specify diff --git a/base/exports.jl b/base/exports.jl index 0e30396010261..c7fd3af088f92 100644 --- a/base/exports.jl +++ b/base/exports.jl @@ -788,6 +788,7 @@ export strip, summary, textwidth, + thisind, titlecase, transcode, ucfirst, diff --git a/base/strings/basic.jl b/base/strings/basic.jl index c04e56eb9a6ac..cc09f8dce7c2c 100644 --- a/base/strings/basic.jl +++ b/base/strings/basic.jl @@ -232,6 +232,39 @@ end ## Generic indexing functions ## +""" + thisind(str::AbstractString, i::Integer) + +Get the largest valid string index at or before `i`. +Returns `0` if there is no valid string index at or before `i`. +Returns `endof(str)` if `i≥endof(str)`. + +# Examples +```jldoctest +julia> thisind("αβγdef", -5) +0 + +julia> thisind("αβγdef", 1) +1 + +julia> thisind("αβγdef", 3) +3 + +julia> thisind("αβγdef", 4) +3 + +julia> thisind("αβγdef", 20) +9 +""" +function thisind(s::AbstractString, i::Integer) + j = Int(i) + isvalid(s, j) && return j + j < start(s) && return 0 + e = endof(s) + j >= endof(s) && return e + prevind(s, j) +end + """ prevind(str::AbstractString, i::Integer, nchar::Integer=1) @@ -249,7 +282,6 @@ julia> prevind("αβγdef", 1) julia> prevind("αβγdef", 3, 2) 0 - ``` """ function prevind(s::AbstractString, i::Integer) diff --git a/base/strings/string.jl b/base/strings/string.jl index 45d1a05b352ca..c5c998f7f4419 100644 --- a/base/strings/string.jl +++ b/base/strings/string.jl @@ -104,7 +104,18 @@ function ==(a::String, b::String) al == sizeof(b) && 0 == ccall(:memcmp, Int32, (Ptr{UInt8}, Ptr{UInt8}, UInt), a, b, al) end -## prevind and nextind ## +## thisind, prevind and nextind ## + +function thisind(s::String, i::Integer) + j = Int(i) + j < 1 && return 0 + e = endof(s) + j >= e && return e + @inbounds while j > 0 && is_valid_continuation(codeunit(s,j)) + j -= 1 + end + j +end function prevind(s::String, i::Integer) j = Int(i) diff --git a/base/strings/types.jl b/base/strings/types.jl index 382b33d5d1698..e3eb6d98312b8 100644 --- a/base/strings/types.jl +++ b/base/strings/types.jl @@ -86,6 +86,20 @@ function isvalid(s::SubString, i::Integer) return (start(s) <= i <= endof(s)) && isvalid(s.string, s.offset+i) end +function thisind(s::SubString{String}, i::Integer) + j = Int(i) + j < 1 && return 0 + e = endof(s) + j >= e && return e + offset = s.offset + str = s.string + j += offset + @inbounds while j > offset && is_valid_continuation(codeunit(str, j)) + j -= 1 + end + j-offset +end + nextind(s::SubString, i::Integer) = nextind(s.string, i+s.offset)-s.offset prevind(s::SubString, i::Integer) = prevind(s.string, i+s.offset)-s.offset diff --git a/doc/src/stdlib/strings.md b/doc/src/stdlib/strings.md index dca748eb40a2f..36ed901262f92 100644 --- a/doc/src/stdlib/strings.md +++ b/doc/src/stdlib/strings.md @@ -60,6 +60,7 @@ Base.chop Base.chomp Base.ind2chr Base.chr2ind +Base.thisind Base.nextind Base.prevind Base.Random.randstring diff --git a/test/strings/basic.jl b/test/strings/basic.jl index 0c7cdb54824ae..a99d635bdcaae 100644 --- a/test/strings/basic.jl +++ b/test/strings/basic.jl @@ -565,6 +565,32 @@ end @test_throws ParseError parse("\'\\.\'") end +@testset "thisind" begin + let strs = Any["∀α>β:α+1>β", s"∀α>β:α+1>β", + SubString("123∀α>β:α+1>β123", 4, 18), + SubString(s"123∀α>β:α+1>β123", 4, 18)] + for s in strs + @test thisind(s, -2) == 0 + @test thisind(s, 0) == 0 + @test thisind(s, 1) == 1 + @test thisind(s, 2) == 1 + @test thisind(s, 3) == 1 + @test thisind(s, 4) == 4 + @test thisind(s, 5) == 4 + @test thisind(s, 6) == 6 + @test thisind(s, 15) == 15 + @test thisind(s, 16) == 15 + @test thisind(s, 30) == 15 + end + end + + let strs = Any["", s"", SubString("123", 2, 1), SubString(s"123", 2, 1)] + for s in strs, i in -2:2 + @test thisind(s, i) == 0 + end + end +end + @testset "prevind and nextind" begin let strs = Any["∀α>β:α+1>β", GenericString("∀α>β:α+1>β")] for i in 1:2