diff --git a/base/deprecated.jl b/base/deprecated.jl index 4a13cbf8b31b9..bbfe8841cfe5e 100644 --- a/base/deprecated.jl +++ b/base/deprecated.jl @@ -3411,6 +3411,10 @@ end # PR #25113 @deprecate_binding CartesianRange CartesianIndices +@deprecate_moved searchsorted "SortedSearch" +@deprecate_moved searchsortedfirst "SortedSearch" +@deprecate_moved searchsortedlast "SortedSearch" + # END 0.7 deprecations # BEGIN 1.0 deprecations diff --git a/base/exports.jl b/base/exports.jl index 40ee38d9b30e5..319bf42f5a5fd 100644 --- a/base/exports.jl +++ b/base/exports.jl @@ -506,9 +506,6 @@ export rot180, rotl90, rotr90, - searchsorted, - searchsortedfirst, - searchsortedlast, shuffle, shuffle!, size, diff --git a/base/linalg/svd.jl b/base/linalg/svd.jl index e33dfb9167042..8ef566c33f157 100644 --- a/base/linalg/svd.jl +++ b/base/linalg/svd.jl @@ -253,7 +253,7 @@ svdvals(S::SVD{<:Any,T}) where {T} = (S[:S])::Vector{T} # SVD least squares function ldiv!(A::SVD{T}, B::StridedVecOrMat) where T - k = searchsortedlast(A.S, eps(real(T))*A.S[1], rev=true) + k = Base.Sort.searchsortedlast(A.S, eps(real(T))*A.S[1], rev=true) view(A.Vt,1:k,:)' * (view(A.S,1:k) .\ (view(A.U,:,1:k)' * B)) end diff --git a/base/repl/REPLCompletions.jl b/base/repl/REPLCompletions.jl index 345c5a9a8c582..df449fe01f737 100644 --- a/base/repl/REPLCompletions.jl +++ b/base/repl/REPLCompletions.jl @@ -107,7 +107,7 @@ const sorted_keywords = [ "true", "try", "using", "while"] function complete_keyword(s::Union{String,SubString{String}}) - r = searchsorted(sorted_keywords, s) + r = Base.Sort.searchsorted(sorted_keywords, s) i = first(r) n = length(sorted_keywords) while i <= n && startswith(sorted_keywords[i],s) diff --git a/base/sort.jl b/base/sort.jl index f2cff89c34e82..846d58021d20b 100644 --- a/base/sort.jl +++ b/base/sort.jl @@ -15,9 +15,6 @@ import export # also exported by Base # order-only: issorted, - searchsorted, - searchsortedfirst, - searchsortedlast, # order & algorithm: sort, sort!, @@ -287,6 +284,8 @@ if `a` does not contain values equal to `x`. # Examples ```jldoctest +julia> using SortedSearch + julia> a = [4, 3, 2, 1] 4-element Array{Int64,1}: 4 @@ -311,6 +310,8 @@ specified order. Return `length(a) + 1` if `x` is greater than all values in `a` # Examples ```jldoctest +julia> using SortedSearch + julia> searchsortedfirst([1, 2, 4, 5, 14], 4) 3 @@ -331,6 +332,8 @@ be sorted. # Examples ```jldoctest +julia> using SortedSearch + julia> searchsortedlast([1, 2, 4, 5, 14], 4) 3 diff --git a/base/sparse/sparsematrix.jl b/base/sparse/sparsematrix.jl index b1c5e587210e9..881b16f8f5cf5 100644 --- a/base/sparse/sparsematrix.jl +++ b/base/sparse/sparsematrix.jl @@ -1,5 +1,7 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license +using Base.Sort: searchsortedfirst, searchsortedlast + # Compressed sparse columns data structure # Assumes that no zeros are stored in the data structure # Assumes that row values in rowval for each column are sorted diff --git a/base/sysimg.jl b/base/sysimg.jl index 0bf997db7299c..d06a449982c60 100644 --- a/base/sysimg.jl +++ b/base/sysimg.jl @@ -495,6 +495,7 @@ Base.require(:SuiteSparse) Base.require(:Test) Base.require(:Unicode) Base.require(:Distributed) +Base.require(:SortedSearch) @eval Base begin @deprecate_binding Test root_module(:Test) true ", run `using Test` instead" diff --git a/base/version.jl b/base/version.jl index 9e18099faeae4..87b80a985f579 100644 --- a/base/version.jl +++ b/base/version.jl @@ -194,7 +194,7 @@ function check_new_version(existing::Vector{VersionNumber}, ver::VersionNumber) end error("$ver is not a valid initial version (try 0.0.0, 0.0.1, 0.1 or 1.0)") end - idx = searchsortedlast(existing, ver) + idx = Base.Sort.searchsortedlast(existing, ver) prv = existing[idx] ver == prv && error("version $ver already exists") nxt = thismajor(ver) != thismajor(prv) ? nextmajor(prv) : diff --git a/doc/make.jl b/doc/make.jl index 7740b38100622..9fe0cfe05db86 100644 --- a/doc/make.jl +++ b/doc/make.jl @@ -33,6 +33,7 @@ if Sys.iswindows() cp_q("../stdlib/IterativeEigensolvers/docs/src/index.md", "src/stdlib/iterativeeigensolvers.md") cp_q("../stdlib/Unicode/docs/src/index.md", "src/stdlib/unicode.md") cp_q("../stdlib/Distributed/docs/src/index.md", "src/stdlib/distributed.md") + cp_q("../stdlib/SortedSearch/docs/src/index.md", "src/stdlib/sortedsearch.md") else symlink_q("../../../stdlib/DelimitedFiles/docs/src/index.md", "src/stdlib/delimitedfiles.md") symlink_q("../../../stdlib/Test/docs/src/index.md", "src/stdlib/test.md") @@ -46,6 +47,7 @@ else symlink_q("../../../stdlib/IterativeEigensolvers/docs/src/index.md", "src/stdlib/iterativeeigensolvers.md") symlink_q("../../../stdlib/Unicode/docs/src/index.md", "src/stdlib/unicode.md") symlink_q("../../../stdlib/Distributed/docs/src/index.md", "src/stdlib/distributed.md") + symlink_q("../../../stdlib/SortedSearch/docs/src/index.md", "src/stdlib/sortedsearch.md") end const PAGES = [ @@ -125,6 +127,7 @@ const PAGES = [ "stdlib/crc32c.md", "stdlib/iterativeeigensolvers.md", "stdlib/unicode.md", + "stdlib/sortedsearch.md", ], "Developer Documentation" => [ "devdocs/reflection.md", @@ -160,12 +163,13 @@ const PAGES = [ ] using DelimitedFiles, Test, Mmap, SharedArrays, Profile, Base64, FileWatching, CRC32c, - Dates, IterativeEigensolvers, Unicode, Distributed + Dates, IterativeEigensolvers, Unicode, Distributed, SortedSearch makedocs( build = joinpath(pwd(), "_build/html/en"), modules = [Base, Core, BuildSysImg, DelimitedFiles, Test, Mmap, SharedArrays, Profile, - Base64, FileWatching, Dates, IterativeEigensolvers, Unicode, Distributed], + Base64, FileWatching, Dates, IterativeEigensolvers, Unicode, Distributed, + SortedSearch], clean = false, doctest = "doctest" in ARGS, linkcheck = "linkcheck" in ARGS, diff --git a/doc/src/index.md b/doc/src/index.md index 816e27014cb3f..aa5cf0e48eea3 100644 --- a/doc/src/index.md +++ b/doc/src/index.md @@ -76,6 +76,7 @@ * [File Events](@ref lib-filewatching) * [Iterative Eigensolvers](@ref lib-itereigen) * [Unicode](@ref) + * [SortedSearch](@ref) ## Developer Documentation diff --git a/doc/src/manual/arrays.md b/doc/src/manual/arrays.md index 2cc353e724c64..88b4416b6224d 100644 --- a/doc/src/manual/arrays.md +++ b/doc/src/manual/arrays.md @@ -273,10 +273,13 @@ julia> x[1, [2 3; 4 1]] ``` Empty ranges of the form `n:n-1` are sometimes used to indicate the inter-index location between -`n-1` and `n`. For example, the [`searchsorted`](@ref) function uses this convention to indicate +`n-1` and `n`. For example, the [`searchsorted`](@ref) function from the standard library +module `SortedSearch` uses this convention to indicate the insertion point of a value not found in a sorted array: ```jldoctest +julia> using SortedSearch + julia> a = [1,2,5,6,7]; julia> searchsorted(a, 3) diff --git a/doc/src/stdlib/.gitignore b/doc/src/stdlib/.gitignore index 7096169fbd716..f15c2c7f3c35f 100644 --- a/doc/src/stdlib/.gitignore +++ b/doc/src/stdlib/.gitignore @@ -8,3 +8,4 @@ filewatching.md crc32c.md dates.md unicode.md +sortedsearch.md \ No newline at end of file diff --git a/doc/src/stdlib/sort.md b/doc/src/stdlib/sort.md index ffb4725b6bcf6..1b499ffa1aad2 100644 --- a/doc/src/stdlib/sort.md +++ b/doc/src/stdlib/sort.md @@ -119,9 +119,6 @@ Base.Sort.sortcols ```@docs Base.issorted -Base.Sort.searchsorted -Base.Sort.searchsortedfirst -Base.Sort.searchsortedlast Base.Sort.partialsort! Base.Sort.partialsort Base.Sort.partialsortperm diff --git a/stdlib/SortedSearch/docs/src/index.md b/stdlib/SortedSearch/docs/src/index.md new file mode 100644 index 0000000000000..d4f956ee3c21a --- /dev/null +++ b/stdlib/SortedSearch/docs/src/index.md @@ -0,0 +1,7 @@ +# SortedSearch + +```@docs +SortedSearch.searchsorted +SortedSearch.searchsortedfirst +SortedSearch.searchsortedlast +``` \ No newline at end of file diff --git a/stdlib/SortedSearch/src/SortedSearch.jl b/stdlib/SortedSearch/src/SortedSearch.jl new file mode 100644 index 0000000000000..420975211a5d2 --- /dev/null +++ b/stdlib/SortedSearch/src/SortedSearch.jl @@ -0,0 +1,11 @@ +# This file is a part of Julia. License is MIT: https://julialang.org/license + +__precompile__(true) + +module SortedSearch + +using Base.Sort: searchsorted, searchsortedfirst, searchsortedlast + +export searchsorted, searchsortedfirst, searchsortedlast + +end diff --git a/stdlib/SortedSearch/test/runtests.jl b/stdlib/SortedSearch/test/runtests.jl new file mode 100644 index 0000000000000..aac3d8c246a99 --- /dev/null +++ b/stdlib/SortedSearch/test/runtests.jl @@ -0,0 +1,97 @@ +# This file is a part of Julia. License is MIT: https://julialang.org/license + +using SortedSearch +using Base.Order: Forward + +@testset "searchsorted" begin + numTypes = [ Int8, Int16, Int32, Int64, Int128, + UInt8, UInt16, UInt32, UInt64, UInt128, + Float16, Float32, Float64, BigInt, BigFloat] + + @test searchsorted([1:10;], 1, by=(x -> x >= 5)) == 1:4 + @test searchsorted([1:10;], 10, by=(x -> x >= 5)) == 5:10 + @test searchsorted([1:5; 1:5; 1:5], 1, 6, 10, Forward) == 6:6 + @test searchsorted(ones(15), 1, 6, 10, Forward) == 6:10 + + for R in numTypes, T in numTypes + @test searchsorted(R[1, 1, 2, 2, 3, 3], T(0)) == 1:0 + @test searchsorted(R[1, 1, 2, 2, 3, 3], T(1)) == 1:2 + @test searchsorted(R[1, 1, 2, 2, 3, 3], T(2)) == 3:4 + @test searchsorted(R[1, 1, 2, 2, 3, 3], T(4)) == 7:6 + @test searchsorted(R[1, 1, 2, 2, 3, 3], 2.5) == 5:4 + + @test searchsorted(1:3, T(0)) == 1:0 + @test searchsorted(1:3, T(1)) == 1:1 + @test searchsorted(1:3, T(2)) == 2:2 + @test searchsorted(1:3, T(4)) == 4:3 + + @test searchsorted(R[1:10;], T(1), by=(x -> x >= 5)) == 1:4 + @test searchsorted(R[1:10;], T(10), by=(x -> x >= 5)) == 5:10 + @test searchsorted(R[1:5; 1:5; 1:5], T(1), 6, 10, Forward) == 6:6 + @test searchsorted(ones(R, 15), T(1), 6, 10, Forward) == 6:10 + end + + for (rg,I) in [(49:57,47:59), (1:2:17,-1:19), (-3:0.5:2,-5:.5:4)] + rg_r = reverse(rg) + rgv, rgv_r = [rg;], [rg_r;] + for i = I + @test searchsorted(rg,i) == searchsorted(rgv,i) + @test searchsorted(rg_r,i,rev=true) == searchsorted(rgv_r,i,rev=true) + end + end + + rg = 0.0:0.01:1.0 + for i = 2:101 + @test searchsorted(rg, rg[i]) == i:i + @test searchsorted(rg, prevfloat(rg[i])) == i:i-1 + @test searchsorted(rg, nextfloat(rg[i])) == i+1:i + end + + rg_r = reverse(rg) + for i = 1:100 + @test searchsorted(rg_r, rg_r[i], rev=true) == i:i + @test searchsorted(rg_r, prevfloat(rg_r[i]), rev=true) == i+1:i + @test searchsorted(rg_r, nextfloat(rg_r[i]), rev=true) == i:i-1 + end + + @test searchsorted(1:10, 1, by=(x -> x >= 5)) == searchsorted([1:10;], 1, by=(x -> x >= 5)) + @test searchsorted(1:10, 10, by=(x -> x >= 5)) == searchsorted([1:10;], 10, by=(x -> x >= 5)) + + @test searchsorted([], 0) == 1:0 + @test searchsorted([1,2,3], 0) == 1:0 + @test searchsorted([1,2,3], 4) == 4:3 + + @testset "issue 8866" begin + @test searchsortedfirst(500:1.0:600, -1.0e20) == 1 + @test searchsortedfirst(500:1.0:600, 1.0e20) == 102 + @test searchsortedlast(500:1.0:600, -1.0e20) == 0 + @test searchsortedlast(500:1.0:600, 1.0e20) == 101 + end +end + +# exercise the codepath in searchsorted* methods for ranges that check for zero step range +struct ConstantRange{T} <: AbstractRange{T} + val::T + len::Int + end + +Base.length(r::ConstantRange) = r.len +Base.getindex(r::ConstantRange, i::Int) = (1 <= i <= r.len || throw(BoundsError(r,i)); r.val) +Base.step(r::ConstantRange) = 0 + +@testset "searchsorted method with ranges which check for zero step range" begin + r = ConstantRange(1, 5) + + @test searchsortedfirst(r, 1.0, Forward) == 1 + @test searchsortedfirst(r, 1, Forward) == 1 + @test searchsortedfirst(r, UInt(1), Forward) == 1 + + @test searchsortedlast(r, 1.0, Forward) == 5 + @test searchsortedlast(r, 1, Forward) == 5 + @test searchsortedlast(r, UInt(1), Forward) == 5 +end + +@testset "issue #19005" begin + @test searchsortedfirst(0:256, 0x80) == 129 + @test searchsortedlast(0:256, 0x80) == 129 +end \ No newline at end of file diff --git a/stdlib/SuiteSparse/src/cholmod.jl b/stdlib/SuiteSparse/src/cholmod.jl index b10e03a79313f..80ed0ffa7ad5a 100644 --- a/stdlib/SuiteSparse/src/cholmod.jl +++ b/stdlib/SuiteSparse/src/cholmod.jl @@ -1259,7 +1259,7 @@ function getindex(A::Sparse{T}, i0::Integer, i1::Integer) where T r1 = Int(unsafe_load(s.p, i1) + 1) r2 = Int(unsafe_load(s.p, i1 + 1)) (r1 > r2) && return zero(T) - r1 = Int(searchsortedfirst(unsafe_wrap(Array, s.i, (s.nzmax,), false), + r1 = Int(Base.Sort.searchsortedfirst(unsafe_wrap(Array, s.i, (s.nzmax,), false), i0 - 1, r1, r2, Base.Order.Forward)) ((r1 > r2) || (unsafe_load(s.i, r1) + 1 != i0)) ? zero(T) : unsafe_load(s.x, r1) end diff --git a/test/compile.jl b/test/compile.jl index da943935af55e..3bd1a9f4b7acc 100644 --- a/test/compile.jl +++ b/test/compile.jl @@ -220,7 +220,7 @@ try Dict(s => Base.module_uuid(Base.root_module(s)) for s in [:Base64, :CRC32c, :Dates, :DelimitedFiles, :FileWatching, :IterativeEigensolvers, :Logging, :Mmap, :Profile, :SharedArrays, - :SuiteSparse, :Test, :Unicode, :Distributed])) + :SuiteSparse, :Test, :Unicode, :Distributed, :SortedSearch])) @test discard_module.(deps) == deps1 @test current_task()(0x01, 0x4000, 0x30031234) == 2 diff --git a/test/sorting.jl b/test/sorting.jl index 9626c5de9e9b1..5e13fa59ff9a3 100644 --- a/test/sorting.jl +++ b/test/sorting.jl @@ -31,92 +31,7 @@ let a=[1:10;] end @test sum(randperm(6)) == 21 -@testset "searchsorted" begin - numTypes = [ Int8, Int16, Int32, Int64, Int128, - UInt8, UInt16, UInt32, UInt64, UInt128, - Float16, Float32, Float64, BigInt, BigFloat] - - @test searchsorted([1:10;], 1, by=(x -> x >= 5)) == 1:4 - @test searchsorted([1:10;], 10, by=(x -> x >= 5)) == 5:10 - @test searchsorted([1:5; 1:5; 1:5], 1, 6, 10, Forward) == 6:6 - @test searchsorted(ones(15), 1, 6, 10, Forward) == 6:10 - - for R in numTypes, T in numTypes - @test searchsorted(R[1, 1, 2, 2, 3, 3], T(0)) == 1:0 - @test searchsorted(R[1, 1, 2, 2, 3, 3], T(1)) == 1:2 - @test searchsorted(R[1, 1, 2, 2, 3, 3], T(2)) == 3:4 - @test searchsorted(R[1, 1, 2, 2, 3, 3], T(4)) == 7:6 - @test searchsorted(R[1, 1, 2, 2, 3, 3], 2.5) == 5:4 - - @test searchsorted(1:3, T(0)) == 1:0 - @test searchsorted(1:3, T(1)) == 1:1 - @test searchsorted(1:3, T(2)) == 2:2 - @test searchsorted(1:3, T(4)) == 4:3 - - @test searchsorted(R[1:10;], T(1), by=(x -> x >= 5)) == 1:4 - @test searchsorted(R[1:10;], T(10), by=(x -> x >= 5)) == 5:10 - @test searchsorted(R[1:5; 1:5; 1:5], T(1), 6, 10, Forward) == 6:6 - @test searchsorted(ones(R, 15), T(1), 6, 10, Forward) == 6:10 - end - - for (rg,I) in [(49:57,47:59), (1:2:17,-1:19), (-3:0.5:2,-5:.5:4)] - rg_r = reverse(rg) - rgv, rgv_r = [rg;], [rg_r;] - for i = I - @test searchsorted(rg,i) == searchsorted(rgv,i) - @test searchsorted(rg_r,i,rev=true) == searchsorted(rgv_r,i,rev=true) - end - end - - rg = 0.0:0.01:1.0 - for i = 2:101 - @test searchsorted(rg, rg[i]) == i:i - @test searchsorted(rg, prevfloat(rg[i])) == i:i-1 - @test searchsorted(rg, nextfloat(rg[i])) == i+1:i - end - - rg_r = reverse(rg) - for i = 1:100 - @test searchsorted(rg_r, rg_r[i], rev=true) == i:i - @test searchsorted(rg_r, prevfloat(rg_r[i]), rev=true) == i+1:i - @test searchsorted(rg_r, nextfloat(rg_r[i]), rev=true) == i:i-1 - end - - @test searchsorted(1:10, 1, by=(x -> x >= 5)) == searchsorted([1:10;], 1, by=(x -> x >= 5)) - @test searchsorted(1:10, 10, by=(x -> x >= 5)) == searchsorted([1:10;], 10, by=(x -> x >= 5)) - - @test searchsorted([], 0) == 1:0 - @test searchsorted([1,2,3], 0) == 1:0 - @test searchsorted([1,2,3], 4) == 4:3 - - @testset "issue 8866" begin - @test searchsortedfirst(500:1.0:600, -1.0e20) == 1 - @test searchsortedfirst(500:1.0:600, 1.0e20) == 102 - @test searchsortedlast(500:1.0:600, -1.0e20) == 0 - @test searchsortedlast(500:1.0:600, 1.0e20) == 101 - end -end -# exercise the codepath in searchsorted* methods for ranges that check for zero step range -struct ConstantRange{T} <: AbstractRange{T} - val::T - len::Int -end - -Base.length(r::ConstantRange) = r.len -Base.getindex(r::ConstantRange, i::Int) = (1 <= i <= r.len || throw(BoundsError(r,i)); r.val) -Base.step(r::ConstantRange) = 0 - -@testset "searchsorted method with ranges which check for zero step range" begin - r = ConstantRange(1, 5) - - @test searchsortedfirst(r, 1.0, Forward) == 1 - @test searchsortedfirst(r, 1, Forward) == 1 - @test searchsortedfirst(r, UInt(1), Forward) == 1 - - @test searchsortedlast(r, 1.0, Forward) == 5 - @test searchsortedlast(r, 1, Forward) == 5 - @test searchsortedlast(r, UInt(1), Forward) == 5 - +@testset "sort, sortperm, sortperm!, permute!, ipermute!, issorted" begin a = rand(1:10000, 1000) for alg in [InsertionSort, MergeSort] b = sort(a, alg=alg) @@ -364,9 +279,5 @@ end @test sort([typemax(Int),typemin(Int)]) == [typemin(Int),typemax(Int)] @test sort([typemax(UInt),0]) == [0,typemax(UInt)] end -@testset "issue #19005" begin - @test searchsortedfirst(0:256, 0x80) == 129 - @test searchsortedlast(0:256, 0x80) == 129 -end # https://discourse.julialang.org/t/sorting-big-int-with-v-0-6/1241 @test sort([big(3), big(2)]) == [big(2), big(3)]