From f02395b9cf9f24de803d0e5934cc7ae8b1b32048 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bogumi=C5=82=20Kami=C5=84ski?= Date: Sun, 1 Oct 2017 13:33:31 +0200 Subject: [PATCH] Improved nextind and prevind (#23805) * improved prevind and nextind * fix rebase conflicts * change i to j in loop * wrap tests in @testset --- NEWS.md | 3 ++ base/strings/basic.jl | 63 +++++++++++++++++++++++++++++++++++-- base/strings/string.jl | 38 ++++++++++++++++++++++ test/strings/basic.jl | 71 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 173 insertions(+), 2 deletions(-) diff --git a/NEWS.md b/NEWS.md index e34b72f9f435f..4419d4d7f67e9 100644 --- a/NEWS.md +++ b/NEWS.md @@ -236,6 +236,9 @@ This section lists changes that do not have deprecation warnings. Library improvements -------------------- + * The functions `nextind` and `prevind` now accept `nchar` argument that indicates + number of characters to move ([#23805]). + * The functions `strip`, `lstrip` and `rstrip` now return `SubString` ([#22496]). * The functions `strwidth` and `charwidth` have been merged into `textwidth`([#20816]). diff --git a/base/strings/basic.jl b/base/strings/basic.jl index 29b639e5b42b3..c6c572882ce66 100644 --- a/base/strings/basic.jl +++ b/base/strings/basic.jl @@ -239,11 +239,23 @@ end prevind(s::DirectIndexString, i::Integer) = Int(i)-1 nextind(s::DirectIndexString, i::Integer) = Int(i)+1 +function prevind(s::DirectIndexString, i::Integer, nchar::Integer) + nchar > 0 || throw(ArgumentError("nchar must be greater than 0")) + Int(i)-nchar +end + +function nextind(s::DirectIndexString, i::Integer, nchar::Integer) + nchar > 0 || throw(ArgumentError("nchar must be greater than 0")) + Int(i)+nchar +end + + """ - prevind(str::AbstractString, i::Integer) + prevind(str::AbstractString, i::Integer, nchar::Integer=1) Get the previous valid string index before `i`. Returns a value less than `1` at the beginning of the string. +If the `nchar` argument is given the function goes back `nchar` characters. # Examples ```jldoctest @@ -252,6 +264,10 @@ julia> prevind("αβγdef", 3) julia> prevind("αβγdef", 1) 0 + +julia> prevind("αβγdef", 3, 2) +0 + ``` """ function prevind(s::AbstractString, i::Integer) @@ -269,11 +285,32 @@ function prevind(s::AbstractString, i::Integer) return 0 # out of range end +function prevind(s::AbstractString, i::Integer, nchar::Integer) + nchar > 0 || throw(ArgumentError("nchar must be greater than 0")) + e = endof(s) + j = Int(i) + j < 1 && return 0 + while nchar > 0 + if j > e + j = e + else + j -= 1 + while j >= 1 && !isvalid(s,j) + j -= 1 + end + end + j < 1 && return 0 + nchar -= 1 + end + j +end + """ - nextind(str::AbstractString, i::Integer) + nextind(str::AbstractString, i::Integer, nchar::Integer=1) Get the next valid string index after `i`. Returns a value greater than `endof(str)` at or after the end of the string. +If the `nchar` argument is given the function goes forward `nchar` characters. # Examples ```jldoctest @@ -282,6 +319,9 @@ julia> str = "αβγdef"; julia> nextind(str, 1) 3 +julia> nextind(str, 1, 2) +5 + julia> endof(str) 9 @@ -305,6 +345,25 @@ function nextind(s::AbstractString, i::Integer) next(s,e)[2] # out of range end +function nextind(s::AbstractString, i::Integer, nchar::Integer) + nchar > 0 || throw(ArgumentError("nchar must be greater than 0")) + e = endof(s) + j = Int(i) + while nchar > 0 + if j < 1 + j = 1 + else + j > e && return j + nchar + j == e && return next(s,e)[2] + nchar - 1 + for j = j+1:e + isvalid(s,j) && break + end + end + nchar -= 1 + end + j +end + checkbounds(s::AbstractString, i::Integer) = start(s) <= i <= endof(s) || throw(BoundsError(s, i)) checkbounds(s::AbstractString, r::AbstractRange{<:Integer}) = isempty(r) || (minimum(r) >= start(s) && maximum(r) <= endof(s)) || throw(BoundsError(s, r)) # The following will end up using a deprecated checkbounds, when the covariant parameter is not Integer diff --git a/base/strings/string.jl b/base/strings/string.jl index 244642378e6f8..d82ef2ce1ecd9 100644 --- a/base/strings/string.jl +++ b/base/strings/string.jl @@ -108,6 +108,25 @@ function prevind(s::String, i::Integer) j end +function prevind(s::String, i::Integer, nchar::Integer) + nchar > 0 || throw(ArgumentError("nchar must be greater than 0")) + j = Int(i) + e = sizeof(s) + while nchar > 0 + if j > e + j = endof(s) + else + j -= 1 + @inbounds while j > 0 && is_valid_continuation(codeunit(s,j)) + j -= 1 + end + end + nchar -= 1 + j <= 0 && return j - nchar + end + j +end + function nextind(s::String, i::Integer) j = Int(i) if j < 1 @@ -121,6 +140,25 @@ function nextind(s::String, i::Integer) j end +function nextind(s::String, i::Integer, nchar::Integer) + nchar > 0 || throw(ArgumentError("nchar must be greater than 0")) + j = Int(i) + e = sizeof(s) + while nchar > 0 + if j < 1 + j = 1 + else + j += 1 + @inbounds while j <= e && is_valid_continuation(codeunit(s,j)) + j += 1 + end + end + nchar -= 1 + j > e && return j + nchar + end + j +end + ## checking UTF-8 & ACSII validity ## byte_string_classify(data::Vector{UInt8}) = diff --git a/test/strings/basic.jl b/test/strings/basic.jl index 048f6723b0991..2da583f9474b5 100644 --- a/test/strings/basic.jl +++ b/test/strings/basic.jl @@ -552,3 +552,74 @@ end @test_throws ParseError parse("\"\\.\"") @test_throws ParseError parse("\'\\.\'") end + +@testset "prevind and nextind" begin + let strs = Any["∀α>β:α+1>β", GenericString("∀α>β:α+1>β")] + for i in 1:2 + @test prevind(strs[i], 1) == 0 + @test prevind(strs[i], 1, 1) == 0 + @test prevind(strs[i], 2) == 1 + @test prevind(strs[i], 2, 1) == 1 + @test prevind(strs[i], 4) == 1 + @test prevind(strs[i], 4, 1) == 1 + @test prevind(strs[i], 5) == 4 + @test prevind(strs[i], 5, 1) == 4 + @test prevind(strs[i], 5, 2) == 1 + @test prevind(strs[i], 5, 3) == 0 + @test prevind(strs[i], 15) == 14 + @test prevind(strs[i], 15, 1) == 14 + @test prevind(strs[i], 15, 2) == 13 + @test prevind(strs[i], 15, 3) == 12 + @test prevind(strs[i], 15, 4) == 10 + @test prevind(strs[i], 15, 10) == 0 + @test prevind(strs[i], 15, 9) == 1 + @test prevind(strs[i], 15, 10) == 0 + @test prevind(strs[i], 16) == 15 + @test prevind(strs[i], 16, 1) == 15 + @test prevind(strs[i], 16, 2) == 14 + @test prevind(strs[i], 20) == 15 + @test prevind(strs[i], 20, 1) == 15 + @test prevind(strs[i], 20, 10) == 1 + @test_throws ArgumentError prevind(strs[i], 20, 0) + + @test nextind(strs[i], -1) == 1 + @test nextind(strs[i], -1, 1) == 1 + @test nextind(strs[i], 0, 2) == 4 + @test nextind(strs[i], 0, 20) == 26 + @test nextind(strs[i], 0, 10) == 15 + @test nextind(strs[i], 1) == 4 + @test nextind(strs[i], 1, 1) == 4 + @test nextind(strs[i], 1, 2) == 6 + @test nextind(strs[i], 1, 9) == 15 + @test nextind(strs[i], 1, 10) == 17 + @test nextind(strs[i], 2) == 4 + @test nextind(strs[i], 2, 1) == 4 + @test nextind(strs[i], 3) == 4 + @test nextind(strs[i], 3, 1) == 4 + @test nextind(strs[i], 4) == 6 + @test nextind(strs[i], 4, 1) == 6 + @test nextind(strs[i], 14) == 15 + @test nextind(strs[i], 14, 1) == 15 + @test nextind(strs[i], 15) == 17 + @test nextind(strs[i], 15, 1) == 17 + @test nextind(strs[i], 20) == 21 + @test nextind(strs[i], 20, 1) == 21 + @test_throws ArgumentError nextind(strs[i], 20, 0) + + for x in -10:20 + n = p = x + for j in 1:40 + p = prevind(strs[i], p) + @test prevind(strs[i], x, j) == p + n = nextind(strs[i], n) + @test nextind(strs[i], x, j) == n + end + end + end + @test prevind(strs[1], -1) == -2 + @test prevind(strs[1], -1, 1) == -2 + + @test prevind(strs[2], -1) == 0 + @test prevind(strs[2], -1, 1) == 0 + end +end