diff --git a/NEWS.md b/NEWS.md index 92790c54e5b35..5ec2e0b9ccfa9 100644 --- a/NEWS.md +++ b/NEWS.md @@ -133,6 +133,9 @@ Standard library changes * An "IPython mode" which mimics the behaviour of the prompts and storing the evaluated result in `Out` can be activated with `REPL.ipython_mode!()`. See the manual for how to enable this at startup. +#### Sort +* `Sort` has moved out of base and is now a standard library. ([#46679]) + #### SparseArrays #### Test diff --git a/base/Base.jl b/base/Base.jl index 63728fdba3e4e..f74476ce03aa8 100644 --- a/base/Base.jl +++ b/base/Base.jl @@ -346,10 +346,8 @@ include("ordering.jl") using .Order # Combinatorics -include("sort.jl") -using .Sort -# BinaryPlatforms, used by Artifacts. Needs `Sort`. +# BinaryPlatforms, used by Artifacts. include("binaryplatforms.jl") # Fast math @@ -465,8 +463,6 @@ for match = _methods(+, (Int, Int), -1, get_world_counter()) Dict("abc" => Set())["abc"] pushfirst!([], sum) get(Base.pkgorigins, Base.PkgId(Base), nothing) - sort!([1,2,3]) - unique!([1,2,3]) cumsum([1,2,3]) append!(Int[], BitSet()) isempty(BitSet()) @@ -477,7 +473,7 @@ for match = _methods(+, (Int, Int), -1, get_world_counter()) any(t->t[1].line > 1, [(LineNumberNode(2,:none), :(1+1))]) # Code loading uses this - sortperm(mtime.(readdir(".")), rev=true) + mtime.(readdir(".", sort=false)) # JLLWrappers uses these Dict{UUID,Set{String}}()[UUID("692b3bcd-3c85-4b1f-b108-f13ce0eb3210")] = Set{String}() get!(Set{String}, Dict{UUID,Set{String}}(), UUID("692b3bcd-3c85-4b1f-b108-f13ce0eb3210")) diff --git a/base/bitset.jl b/base/bitset.jl index 8727b857bd36b..435898e984b8a 100644 --- a/base/bitset.jl +++ b/base/bitset.jl @@ -430,4 +430,3 @@ end minimum(s::BitSet) = first(s) maximum(s::BitSet) = last(s) extrema(s::BitSet) = (first(s), last(s)) -issorted(s::BitSet) = true diff --git a/base/compiler/compiler.jl b/base/compiler/compiler.jl index 3c41c353e86ad..41f1eeedd3988 100644 --- a/base/compiler/compiler.jl +++ b/base/compiler/compiler.jl @@ -121,12 +121,10 @@ import Core.Compiler.CoreDocs Core.atdoc!(CoreDocs.docm) # sorting -function sort! end function issorted end include("ordering.jl") using .Order -include("sort.jl") -using .Sort +include("compiler/sort.jl") # We don't include some.jl, but this definition is still useful. something(x::Nothing, y...) = something(y...) diff --git a/base/compiler/sort.jl b/base/compiler/sort.jl new file mode 100644 index 0000000000000..71d2f8a51cd59 --- /dev/null +++ b/base/compiler/sort.jl @@ -0,0 +1,100 @@ +# This file is a part of Julia. License is MIT: https://julialang.org/license + +# reference on sorted binary search: +# http://www.tbray.org/ongoing/When/200x/2003/03/22/Binary + +# index of the first value of vector a that is greater than or equal to x; +# returns lastindex(v)+1 if x is greater than all values in v. +function searchsortedfirst(v::AbstractVector, x, lo::T, hi::T, o::Ordering)::keytype(v) where T<:Integer + hi = hi + T(1) + len = hi - lo + @inbounds while len != 0 + half_len = len >>> 0x01 + m = lo + half_len + if lt(o, v[m], x) + lo = m + 1 + len -= half_len + 1 + else + hi = m + len = half_len + end + end + return lo +end + +# index of the last value of vector a that is less than or equal to x; +# returns firstindex(v)-1 if x is less than all values of v. +function searchsortedlast(v::AbstractVector, x, lo::T, hi::T, o::Ordering)::keytype(v) where T<:Integer + u = T(1) + lo = lo - u + hi = hi + u + @inbounds while lo < hi - u + m = midpoint(lo, hi) + if lt(o, x, v[m]) + hi = m + else + lo = m + end + end + return lo +end + +# returns the range of indices of v equal to x +# if v does not contain x, returns a 0-length range +# indicating the insertion point of x +function searchsorted(v::AbstractVector, x, ilo::T, ihi::T, o::Ordering)::UnitRange{keytype(v)} where T<:Integer + u = T(1) + lo = ilo - u + hi = ihi + u + @inbounds while lo < hi - u + m = midpoint(lo, hi) + if lt(o, v[m], x) + lo = m + elseif lt(o, x, v[m]) + hi = m + else + a = searchsortedfirst(v, x, max(lo,ilo), m, o) + b = searchsortedlast(v, x, m, min(hi,ihi), o) + return a : b + end + end + return (lo + 1) : (hi - 1) +end + +for s in [:searchsortedfirst, :searchsortedlast, :searchsorted] + @eval begin + $s(v::AbstractVector, x, o::Ordering) = $s(v,x,firstindex(v),lastindex(v),o) + $s(v::AbstractVector, x; + lt=isless, by=identity, rev::Union{Bool,Nothing}=nothing, order::Ordering=Forward) = + $s(v,x,ord(lt,by,rev,order)) + end +end + +# An unstable sorting algorithm for internal use +function sort!(v::Vector; by::Function=identity, (<)::Function=<) + isempty(v) && return v # This branch is hit 95% of the time + + # Of the remaining 5%, this branch is hit less than 1% of the time + if length(v) > 200 # Heap sort prevents quadratic runtime + o = ord(<, by, true) + heapify!(v, o) + for i in lastindex(v):-1:2 + y = v[i] + v[i] = v[1] + percolate_down!(v, 1, y, o, i-1) + end + return v + end + + @inbounds for i in 2:length(v) # Insertion sort + x = v[i] + y = by(x) + while i > 1 && y < by(v[i-1]) + v[i] = v[i-1] + i -= 1 + end + v[i] = x + end + + v +end diff --git a/base/compiler/ssair/ir.jl b/base/compiler/ssair/ir.jl index aad00edebaaca..400da8fb5ae04 100644 --- a/base/compiler/ssair/ir.jl +++ b/base/compiler/ssair/ir.jl @@ -534,16 +534,6 @@ end insert_node!(ir::IRCode, pos::Int, inst::NewInstruction, attach_after::Bool=false) = insert_node!(ir, SSAValue(pos), inst, attach_after) -# For bootstrapping -function my_sortperm(v) - p = Vector{Int}(undef, length(v)) - for i = 1:length(v) - p[i] = i - end - sort!(p, Sort.DEFAULT_UNSTABLE, Order.Perm(Sort.Forward,v)) - p -end - mutable struct IncrementalCompact ir::IRCode result::InstructionStream @@ -560,9 +550,8 @@ mutable struct IncrementalCompact # This supports insertion while compacting new_new_nodes::NewNodeStream # New nodes that were before the compaction point at insertion time new_new_used_ssas::Vector{Int} - # TODO: Switch these two to a min-heap of some sort pending_nodes::NewNodeStream # New nodes that were after the compaction point at insertion time - pending_perm::Vector{Int} + pending_perm::Vector{Int} # pending_nodes.info[pending_perm] is in min-heap order by pos # State idx::Int @@ -574,10 +563,9 @@ mutable struct IncrementalCompact function IncrementalCompact(code::IRCode, allow_cfg_transforms::Bool=false) # Sort by position with attach after nodes after regular ones - perm = my_sortperm(Int[let new_node = code.new_nodes.info[i] - (new_node.pos * 2 + Int(new_node.attach_after)) - end for i in 1:length(code.new_nodes)]) - new_len = length(code.stmts) + length(code.new_nodes) + info = code.new_nodes.info + perm = sort!(collect(eachindex(info)); by=i->(2info[i].pos+info[i].attach_after, i)) + new_len = length(code.stmts) + length(info) result = InstructionStream(new_len) used_ssas = fill(0, new_len) new_new_used_ssas = Vector{Int}() @@ -629,8 +617,9 @@ mutable struct IncrementalCompact # For inlining function IncrementalCompact(parent::IncrementalCompact, code::IRCode, result_offset) - perm = my_sortperm(Int[code.new_nodes.info[i].pos for i in 1:length(code.new_nodes)]) - new_len = length(code.stmts) + length(code.new_nodes) + info = code.new_nodes.info + perm = sort!(collect(eachindex(info)); by=i->(info[i].pos, i)) + new_len = length(code.stmts) + length(info) ssa_rename = Any[SSAValue(i) for i = 1:new_len] bb_rename = Vector{Int}() pending_nodes = NewNodeStream() @@ -769,9 +758,7 @@ end function add_pending!(compact::IncrementalCompact, pos::Int, attach_after::Bool) node = add!(compact.pending_nodes, pos, attach_after) - # TODO: switch this to `l = length(pending_nodes); splice!(pending_perm, searchsorted(pending_perm, l), l)` - push!(compact.pending_perm, length(compact.pending_nodes)) - sort!(compact.pending_perm, DEFAULT_STABLE, Order.By(x->compact.pending_nodes.info[x].pos, Order.Forward)) + heappush!(compact.pending_perm, length(compact.pending_nodes), By(x -> compact.pending_nodes.info[x].pos)) return node end @@ -1456,7 +1443,7 @@ function iterate_compact(compact::IncrementalCompact, (idx, active_bb)::Tuple{In if !(info.attach_after ? info.pos <= compact.idx - 1 : info.pos <= compact.idx) break end - popfirst!(compact.pending_perm) + heappop!(compact.pending_perm, By(x -> compact.pending_nodes.info[x].pos)) end # Move to next block compact.idx += 1 @@ -1481,7 +1468,7 @@ function iterate_compact(compact::IncrementalCompact, (idx, active_bb)::Tuple{In elseif !isempty(compact.pending_perm) && (info = compact.pending_nodes.info[compact.pending_perm[1]]; info.attach_after ? info.pos == idx - 1 : info.pos == idx) - new_idx = popfirst!(compact.pending_perm) + new_idx = heappop!(compact.pending_perm, By(x -> compact.pending_nodes.info[x].pos)) new_node_entry = compact.pending_nodes.stmts[new_idx] new_node_info = compact.pending_nodes.info[new_idx] new_idx += length(compact.ir.stmts) + length(compact.ir.new_nodes) diff --git a/base/compiler/ssair/passes.jl b/base/compiler/ssair/passes.jl index d594112b239e3..362a14fefde20 100644 --- a/base/compiler/ssair/passes.jl +++ b/base/compiler/ssair/passes.jl @@ -72,7 +72,7 @@ function try_compute_fieldidx_stmt(ir::Union{IncrementalCompact,IRCode}, stmt::E return try_compute_fieldidx(typ, field) end -function find_curblock(domtree::DomTree, allblocks::Vector{Int}, curblock::Int) +function find_curblock(domtree::DomTree, allblocks::BitSet, curblock::Int) # TODO: This can be much faster by looking at current level and only # searching for those blocks in a sorted order while !(curblock in allblocks) && curblock !== 0 @@ -92,7 +92,7 @@ function val_for_def_expr(ir::IRCode, def::Int, fidx::Int) end end -function compute_value_for_block(ir::IRCode, domtree::DomTree, allblocks::Vector{Int}, du::SSADefUse, phinodes::IdDict{Int, SSAValue}, fidx::Int, curblock::Int) +function compute_value_for_block(ir::IRCode, domtree::DomTree, allblocks::BitSet, du::SSADefUse, phinodes::IdDict{Int, SSAValue}, fidx::Int, curblock::Int) curblock = find_curblock(domtree, allblocks, curblock) def = 0 for stmt in du.defs @@ -103,7 +103,7 @@ function compute_value_for_block(ir::IRCode, domtree::DomTree, allblocks::Vector def == 0 ? phinodes[curblock] : val_for_def_expr(ir, def, fidx) end -function compute_value_for_use(ir::IRCode, domtree::DomTree, allblocks::Vector{Int}, +function compute_value_for_use(ir::IRCode, domtree::DomTree, allblocks::BitSet, du::SSADefUse, phinodes::IdDict{Int, SSAValue}, fidx::Int, use::Int) def, useblock, curblock = find_def_for_use(ir, domtree, allblocks, du, use) if def == 0 @@ -122,7 +122,7 @@ end # even when the allocation contains an uninitialized field, we try an extra effort to check # if this load at `idx` have any "safe" `setfield!` calls that define the field function has_safe_def( - ir::IRCode, domtree::DomTree, allblocks::Vector{Int}, du::SSADefUse, + ir::IRCode, domtree::DomTree, allblocks::BitSet, du::SSADefUse, newidx::Int, idx::Int) def, _, _ = find_def_for_use(ir, domtree, allblocks, du, idx) # will throw since we already checked this `:new` site doesn't define this field @@ -157,7 +157,7 @@ end # find the first dominating def for the given use function find_def_for_use( - ir::IRCode, domtree::DomTree, allblocks::Vector{Int}, du::SSADefUse, use::Int, inclusive::Bool=false) + ir::IRCode, domtree::DomTree, allblocks::BitSet, du::SSADefUse, use::Int, inclusive::Bool=false) useblock = block_for_inst(ir.cfg, use) curblock = find_curblock(domtree, allblocks, useblock) local def = 0 @@ -1306,7 +1306,7 @@ function sroa_mutables!(ir::IRCode, defuses::IdDict{Int, Tuple{SPCSet, SSADefUse # but we should come up with semantics for well defined semantics # for uninitialized fields first. ndefuse = length(fielddefuse) - blocks = Vector{Tuple{#=phiblocks=# Vector{Int}, #=allblocks=# Vector{Int}}}(undef, ndefuse) + blocks = Vector{Tuple{#=phiblocks=# Vector{Int}, #=allblocks=# BitSet}}(undef, ndefuse) for fidx in 1:ndefuse du = fielddefuse[fidx] isempty(du.uses) && continue @@ -1317,7 +1317,7 @@ function sroa_mutables!(ir::IRCode, defuses::IdDict{Int, Tuple{SPCSet, SSADefUse else phiblocks = iterated_dominance_frontier(ir.cfg, ldu, get!(lazydomtree)) end - allblocks = sort!(vcat(phiblocks, ldu.def_bbs); alg=QuickSort) + allblocks = union!(BitSet(phiblocks), ldu.def_bbs) blocks[fidx] = phiblocks, allblocks if fidx + 1 > length(defexpr.args) for i = 1:length(du.uses) diff --git a/base/cpuid.jl b/base/cpuid.jl index 48930d8064ba9..8831a33799fb0 100644 --- a/base/cpuid.jl +++ b/base/cpuid.jl @@ -95,7 +95,7 @@ let arch = normalize_arch(String(Sys.ARCH)) if arch in keys(ISAs_by_family) for isa in ISAs_by_family[arch] - unique!(append!(FEATURES, last(isa).features)) + Base._unique!(append!(FEATURES, last(isa).features)) end end diff --git a/base/range.jl b/base/range.jl index b9625aac3c443..5ea36e0b39527 100644 --- a/base/range.jl +++ b/base/range.jl @@ -1375,19 +1375,6 @@ function _reverse(r::StepRangeLen, ::Colon) end _reverse(r::LinRange{T}, ::Colon) where {T} = typeof(r)(r.stop, r.start, length(r)) -## sorting ## - -issorted(r::AbstractUnitRange) = true -issorted(r::AbstractRange) = length(r) <= 1 || step(r) >= zero(step(r)) - -sort(r::AbstractUnitRange) = r -sort!(r::AbstractUnitRange) = r - -sort(r::AbstractRange) = issorted(r) ? r : reverse(r) - -sortperm(r::AbstractUnitRange) = 1:length(r) -sortperm(r::AbstractRange) = issorted(r) ? (1:1:length(r)) : (length(r):-1:1) - function sum(r::AbstractRange{<:Real}) l = length(r) # note that a little care is required to avoid overflow in l*(l-1)/2 diff --git a/base/reflection.jl b/base/reflection.jl index d6c044103d0bc..fbea4a3ccf23f 100644 --- a/base/reflection.jl +++ b/base/reflection.jl @@ -100,7 +100,9 @@ since it is not idiomatic to explicitly export names from `Main`. See also: [`@locals`](@ref Base.@locals), [`@__MODULE__`](@ref). """ names(m::Module; all::Bool = false, imported::Bool = false) = - sort!(ccall(:jl_module_names, Array{Symbol,1}, (Any, Cint, Cint), m, all, imported)) + sort!(unsorted_names(m; all, imported)) +unsorted_names(m::Module; all::Bool = false, imported::Bool = false) = + ccall(:jl_module_names, Array{Symbol,1}, (Any, Cint, Cint), m, all, imported) isexported(m::Module, s::Symbol) = ccall(:jl_module_exports_p, Cint, (Any, Any), m, s) != 0 isdeprecated(m::Module, s::Symbol) = ccall(:jl_is_binding_deprecated, Cint, (Any, Any), m, s) != 0 diff --git a/base/sysimg.jl b/base/sysimg.jl index 5a14bf5bfd3b9..fe3ebfd22f99e 100644 --- a/base/sysimg.jl +++ b/base/sysimg.jl @@ -49,14 +49,15 @@ let :Distributed, :Future, :InteractiveUtils, - :LibGit2, :Profile, - :SparseArrays, + :Sort, :UUIDs, # 3-depth packages + :LibGit2, :REPL, :SharedArrays, + :SparseArrays, :TOML, :Test, diff --git a/base/util.jl b/base/util.jl index f26ed0717a1fd..91a3672b9512d 100644 --- a/base/util.jl +++ b/base/util.jl @@ -48,10 +48,9 @@ const disable_text_style = Dict{Symbol,String}( # of colors. let color_syms = collect(Iterators.filter(x -> !isa(x, Integer), keys(text_colors))), formatting_syms = [:normal, :bold, :default] - global const available_text_colors = cat( - sort!(intersect(color_syms, formatting_syms), rev=true), - sort!(setdiff( color_syms, formatting_syms)); - dims=1) + global const available_text_colors = vcat( + Core.Compiler.sort!(intersect(color_syms, formatting_syms); < = >), #reverse + Core.Compiler.sort!( setdiff(color_syms, formatting_syms); <)) end const available_text_colors_docstring = diff --git a/contrib/print_sorted_stdlibs.jl b/contrib/print_sorted_stdlibs.jl index bbf890328cb4e..c9c24f921879c 100644 --- a/contrib/print_sorted_stdlibs.jl +++ b/contrib/print_sorted_stdlibs.jl @@ -27,8 +27,8 @@ end project_deps = Dict{String,Set{String}}() for project_dir in readdir(STDLIB_DIR, join=true) - files = readdir(project_dir) - if "Project.toml" in files + project_file = joinpath(project_dir, "Project.toml") + if isfile(project_file) project = TOML.parsefile(joinpath(project_dir, "Project.toml")) if !haskey(project, "name") diff --git a/doc/make.jl b/doc/make.jl index 61adf2ec603fa..0ecc73d1fcc03 100644 --- a/doc/make.jl +++ b/doc/make.jl @@ -116,7 +116,6 @@ BaseDocs = [ "base/file.md", "base/io-network.md", "base/punctuation.md", - "base/sort.md", "base/iterators.md", "base/c.md", "base/libc.md", diff --git a/stdlib/LibGit2/Project.toml b/stdlib/LibGit2/Project.toml index da78f70fa1005..3398d93f7d42d 100644 --- a/stdlib/LibGit2/Project.toml +++ b/stdlib/LibGit2/Project.toml @@ -5,6 +5,7 @@ uuid = "76f85450-5226-5b5a-8eaa-529ad045b433" Base64 = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" NetworkOptions = "ca575930-c2e3-43a9-ace4-1e988b2c1908" Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7" +Sort = "a93dcad4-a5b3-87f1-1599-dc5d35b4bf41" SHA = "ea8e919c-243c-51af-8825-aaa63cd721ce" [extras] diff --git a/stdlib/Makefile b/stdlib/Makefile index 7957520c31ea3..bef8eef869add 100644 --- a/stdlib/Makefile +++ b/stdlib/Makefile @@ -44,7 +44,7 @@ $(foreach jll,$(JLLS),$(eval $(call download-artifacts-toml,$(jll)))) STDLIBS = Artifacts Base64 CRC32c Dates Distributed FileWatching \ Future InteractiveUtils LazyArtifacts Libdl LibGit2 LinearAlgebra Logging \ Markdown Mmap Printf Profile Random REPL Serialization SHA \ - SharedArrays Sockets SparseArrays SuiteSparse Test TOML Unicode UUIDs \ + SharedArrays Sockets Sort SparseArrays SuiteSparse Test TOML Unicode UUIDs \ $(JLL_NAMES) STDLIBS_EXT = Pkg Statistics LibCURL Downloads ArgTools Tar NetworkOptions SuiteSparse SparseArrays SHA diff --git a/stdlib/Sort/Project.toml b/stdlib/Sort/Project.toml new file mode 100644 index 0000000000000..69d4c49fe58f6 --- /dev/null +++ b/stdlib/Sort/Project.toml @@ -0,0 +1,11 @@ +name = "Sort" +uuid = "a93dcad4-a5b3-87f1-1599-dc5d35b4bf41" + +[deps] +Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" + +[extras] +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" + +[targets] +test = ["Test"] diff --git a/doc/src/base/sort.md b/stdlib/Sort/docs/src/index.md similarity index 92% rename from doc/src/base/sort.md rename to stdlib/Sort/docs/src/index.md index 9f00381ab892c..93bd45e2002d3 100644 --- a/doc/src/base/sort.md +++ b/stdlib/Sort/docs/src/index.md @@ -107,39 +107,39 @@ can be specified via the `lt` keyword. ## Sorting Functions ```@docs -Base.sort! -Base.sort -Base.sortperm -Base.InsertionSort -Base.MergeSort -Base.QuickSort -Base.PartialQuickSort -Base.Sort.sortperm! -Base.Sort.sortslices +sort! +sort +sortperm +Base.Sort.InsertionSort +Base.Sort.MergeSort +Base.Sort.QuickSort +Base.Sort.PartialQuickSort +sortperm! +sortslices ``` ## Order-Related Functions ```@docs -Base.issorted -Base.Sort.searchsorted -Base.Sort.searchsortedfirst -Base.Sort.searchsortedlast -Base.Sort.insorted -Base.Sort.partialsort! -Base.Sort.partialsort -Base.Sort.partialsortperm -Base.Sort.partialsortperm! +issorted +searchsorted +searchsortedfirst +searchsortedlast +insorted +partialsort! +partialsort +partialsortperm +partialsortperm! ``` ## Sorting Algorithms There are currently four sorting algorithms available in base Julia: - * [`InsertionSort`](@ref) - * [`QuickSort`](@ref) - * [`PartialQuickSort(k)`](@ref) - * [`MergeSort`](@ref) + * [`InsertionSort`](@ref Sort.InsertionSort) + * [`QuickSort`](@ref Sort.QuickSort) + * [`PartialQuickSort(k)`](@ref Sort.PartialQuickSort) + * [`MergeSort`](@ref Sort.MergeSort) `InsertionSort` is an O(n^2) stable sorting algorithm. It is efficient for very small `n`, and is used internally by `QuickSort`. diff --git a/base/sort.jl b/stdlib/Sort/src/Sort.jl similarity index 94% rename from base/sort.jl rename to stdlib/Sort/src/Sort.jl index f6f737ac2082e..003b9828101df 100644 --- a/base/sort.jl +++ b/stdlib/Sort/src/Sort.jl @@ -2,26 +2,9 @@ module Sort -import ..@__MODULE__, ..parentmodule -const Base = parentmodule(@__MODULE__) -using .Base.Order -using .Base: copymutable, LinearIndices, length, (:), iterate, OneTo, - eachindex, axes, first, last, similar, zip, OrdinalRange, firstindex, lastindex, - AbstractVector, @inbounds, AbstractRange, @eval, @inline, Vector, @noinline, - AbstractMatrix, AbstractUnitRange, isless, identity, eltype, >, <, <=, >=, |, +, -, *, !, - extrema, sub_with_overflow, add_with_overflow, oneunit, div, getindex, setindex!, - length, resize!, fill, Missing, require_one_based_indexing, keytype, UnitRange, - min, max, reinterpret, signed, unsigned, Signed, Unsigned, typemin, xor, Type, BitSigned, Val, - midpoint, @boundscheck, checkbounds - -using .Base: >>>, !==, != - -import .Base: - sort, - sort!, - issorted, - sortperm, - to_indices +using Base.Order +using Base: copymutable, OneTo, sub_with_overflow, add_with_overflow, + require_one_based_indexing, BitSigned, midpoint export # also exported by Base # order-only: @@ -43,9 +26,8 @@ export # also exported by Base InsertionSort, QuickSort, MergeSort, - PartialQuickSort - -export # not exported by Base + PartialQuickSort, + # not exported by Base Algorithm, DEFAULT_UNSTABLE, DEFAULT_STABLE, @@ -73,7 +55,7 @@ end issorted(v, lt=isless, by=identity, rev::Bool=false, order::Ordering=Forward) Test whether a vector is in sorted order. The `lt`, `by` and `rev` keywords modify what -order is considered to be sorted just as they do for [`sort`](@ref). +order is considered to be sorted just as they do for [`sort`](@ref Base.sort). # Examples ```jldoctest @@ -94,6 +76,8 @@ issorted(itr; lt=isless, by=identity, rev::Union{Bool,Nothing}=nothing, order::Ordering=Forward) = issorted(itr, ord(lt,by,rev,order)) +issorted(::BitSet) = true + function partialsort!(v::AbstractVector, k::Union{Integer,OrdinalRange}, o::Ordering) sort!(v, firstindex(v), lastindex(v), PartialQuickSort(k), o) maybeview(v, k) @@ -160,7 +144,7 @@ partialsort!(v::AbstractVector, k::Union{Integer,OrdinalRange}; """ partialsort(v, k, by=, lt=, rev=false) -Variant of [`partialsort!`](@ref) which copies `v` before partially sorting it, thereby returning the +Variant of [`partialsort!`](@ref Base.partialsort!) which copies `v` before partially sorting it, thereby returning the same thing as `partialsort!` but leaving `v` unmodified. """ partialsort(v::AbstractVector, k::Union{Integer,OrdinalRange}; kws...) = @@ -305,7 +289,7 @@ according to the order specified by the `by`, `lt` and `rev` keywords, assuming is already sorted in that order. Return an empty range located at the insertion point if `a` does not contain values equal to `x`. -See also: [`insorted`](@ref), [`searchsortedfirst`](@ref), [`sort`](@ref), [`findall`](@ref). +See also: [`insorted`](@ref Base.insorted), [`searchsortedfirst`](@ref Base.searchsortedfirst), [`sort`](@ref Base.sort), [`findall`](@ref). # Examples ```jldoctest @@ -335,7 +319,7 @@ specified order. Return `lastindex(a) + 1` if `x` is greater than all values in `insert!`ing `x` at this index will maintain sorted order. -See also: [`searchsortedlast`](@ref), [`searchsorted`](@ref), [`findfirst`](@ref). +See also: [`searchsortedlast`](@ref Base.searchsortedlast), [`searchsorted`](@ref Base.searchsorted), [`findfirst`](@ref). # Examples ```jldoctest @@ -388,7 +372,7 @@ julia> searchsortedlast([1, 2, 4, 5, 5, 7], 0) # no match, insert at start Determine whether an item `x` is in the sorted collection `a`, in the sense that it is [`==`](@ref) to one of the values of the collection according to the order specified by the `by`, `lt` and `rev` keywords, assuming that `a` is already -sorted in that order, see [`sort`](@ref) for the keywords. +sorted in that order, see [`sort`](@ref Base.sort) for the keywords. See also [`in`](@ref). @@ -446,14 +430,14 @@ end Indicate that a sorting function should use the partial quick sort algorithm. Partial quick sort returns the smallest `k` elements sorted from smallest -to largest, finding them and sorting them using [`QuickSort`](@ref). +to largest, finding them and sorting them using [`QuickSort`](@ref Sort.QuickSort). Characteristics: * *not stable*: does not preserve the ordering of elements which compare equal (e.g. "a" and "A" in a sort of letters which ignores case). * *in-place* in memory. - * *divide-and-conquer*: sort strategy similar to [`MergeSort`](@ref). + * *divide-and-conquer*: sort strategy similar to [`MergeSort`](@ref Sort.MergeSort). """ struct PartialQuickSort{T <: Union{Integer,OrdinalRange}} <: Algorithm k::T @@ -976,7 +960,7 @@ end """ sort(v; alg::Algorithm=defalg(v), lt=isless, by=identity, rev::Bool=false, order::Ordering=Forward) -Variant of [`sort!`](@ref) that returns a sorted copy of `v` leaving `v` itself unmodified. +Variant of [`sort!`](@ref Base.sort!) that returns a sorted copy of `v` leaving `v` itself unmodified. # Examples ```jldoctest @@ -1036,7 +1020,7 @@ partialsortperm(v::AbstractVector, k::Union{Integer,OrdinalRange}; kwargs...) = """ partialsortperm!(ix, v, k; by=, lt=, rev=false, initialized=false) -Like [`partialsortperm`](@ref), but accepts a preallocated index vector `ix` the same size as +Like [`partialsortperm`](@ref Base.partialsortperm), but accepts a preallocated index vector `ix` the same size as `v`, which is used to store (a permutation of) the indices of `v`. If the index vector `ix` is initialized with the indices of `v` (or a permutation thereof), `initialized` should be set to @@ -1108,11 +1092,11 @@ end Return a permutation vector or array `I` that puts `A[I]` in sorted order along the given dimension. If `A` has more than one dimension, then the `dims` keyword argument must be specified. The order is specified -using the same keywords as [`sort!`](@ref). The permutation is guaranteed to be stable even +using the same keywords as [`sort!`](@ref Base.sort!). The permutation is guaranteed to be stable even if the sorting algorithm is unstable, meaning that indices of equal elements appear in ascending order. -See also [`sortperm!`](@ref), [`partialsortperm`](@ref), [`invperm`](@ref), [`indexin`](@ref). +See also [`sortperm!`](@ref Base.sortperm!), [`partialsortperm`](@ref Base.partialsortperm), [`invperm`](@ref), [`indexin`](@ref). To sort slices of an array, refer to [`sortslices`](@ref). # Examples @@ -1175,7 +1159,7 @@ end """ sortperm!(ix, A; alg::Algorithm=DEFAULT_UNSTABLE, lt=isless, by=identity, rev::Bool=false, order::Ordering=Forward, initialized::Bool=false, [dims::Integer]) -Like [`sortperm`](@ref), but accepts a preallocated index vector or array `ix` with the same `axes` as `A`. If `initialized` is `false` +Like [`sortperm`](@ref Base.sortperm), but accepts a preallocated index vector or array `ix` with the same `axes` as `A`. If `initialized` is `false` (the default), `ix` is initialized to contain the values `LinearIndices(A)`. # Examples @@ -1257,7 +1241,7 @@ end sort(A; dims::Integer, alg::Algorithm=DEFAULT_UNSTABLE, lt=isless, by=identity, rev::Bool=false, order::Ordering=Forward) Sort a multidimensional array `A` along the given dimension. -See [`sort!`](@ref) for a description of possible +See [`sort!`](@ref Base.sort!) for a description of possible keyword arguments. To sort slices of an array, refer to [`sortslices`](@ref). @@ -1316,7 +1300,7 @@ end sort!(A; dims::Integer, alg::Algorithm=defalg(A), lt=isless, by=identity, rev::Bool=false, order::Ordering=Forward) Sort the multidimensional array `A` along dimension `dims`. -See [`sort!`](@ref) for a description of possible keyword arguments. +See [`sort!`](@ref Base.sort!) for a description of possible keyword arguments. To sort slices of an array, refer to [`sortslices`](@ref). @@ -1416,10 +1400,7 @@ uint_map(x::Signed, ::ForwardOrdering) = uint_unmap(::Type{T}, u::Unsigned, ::ForwardOrdering) where T <: Signed = xor(signed(u), typemin(T)) -# unsigned(Int) is not available during bootstrapping. -for (U, S) in [(UInt8, Int8), (UInt16, Int16), (UInt32, Int32), (UInt64, Int64), (UInt128, Int128)] - @eval UIntMappable(::Union{Type{$U}, Type{$S}}, ::ForwardOrdering) = $U -end +UIntMappable(T::Base.BitIntegerType, ::ForwardOrdering) = unsigned(T) # Floats are not UIntMappable under regular orderings because they fail on NaN edge cases. # uint mappings for floats are defined in Float, where the Left and Right orderings @@ -1461,14 +1442,12 @@ end module Float using ..Sort using ...Order -using ..Base: @inbounds, AbstractVector, Vector, last, firstindex, lastindex, Missing, Type, reinterpret +using Base: IEEEFloat import Core.Intrinsics: slt_int import ..Sort: sort!, UIntMappable, uint_map, uint_unmap import ...Order: lt, DirectOrdering -# IEEEFloat is not available in Core.Compiler -const Floats = Union{Float16, Float32, Float64} # fpsort is not safe for vectors of mixed bitwidth such as Vector{Union{Float32, Float64}}. # This type allows us to dispatch only when it is safe to do so. See #42739 for more info. const FPSortable = Union{ @@ -1489,8 +1468,8 @@ right(::DirectOrdering) = Right() left(o::Perm) = Perm(left(o.order), o.data) right(o::Perm) = Perm(right(o.order), o.data) -lt(::Left, x::T, y::T) where {T<:Floats} = slt_int(y, x) -lt(::Right, x::T, y::T) where {T<:Floats} = slt_int(x, y) +lt(::Left, x::T, y::T) where {T<:IEEEFloat} = slt_int(y, x) +lt(::Right, x::T, y::T) where {T<:IEEEFloat} = slt_int(x, y) uint_map(x::Float16, ::Left) = ~reinterpret(UInt16, x) uint_unmap(::Type{Float16}, u::UInt16, ::Left) = reinterpret(Float16, ~u) @@ -1510,11 +1489,11 @@ uint_map(x::Float64, ::Right) = reinterpret(UInt64, x) uint_unmap(::Type{Float64}, u::UInt64, ::Right) = reinterpret(Float64, u) UIntMappable(::Type{Float64}, ::Union{Left, Right}) = UInt64 -isnan(o::DirectOrdering, x::Floats) = (x!=x) +isnan(o::DirectOrdering, x::IEEEFloat) = (x!=x) isnan(o::DirectOrdering, x::Missing) = false isnan(o::Perm, i::Integer) = isnan(o.order,o.data[i]) -ismissing(o::DirectOrdering, x::Floats) = false +ismissing(o::DirectOrdering, x::IEEEFloat) = false ismissing(o::DirectOrdering, x::Missing) = true ismissing(o::Perm, i::Integer) = ismissing(o.order,o.data[i]) @@ -1586,10 +1565,9 @@ specials2end!(v::AbstractVector{<:Integer}, a::Algorithm, o::Perm{<:ForwardOrder specials2end!(v::AbstractVector{<:Integer}, a::Algorithm, o::Perm{<:ReverseOrdering}) = specials2left!(v, a, o) -issignleft(o::ForwardOrdering, x::Floats) = lt(o, x, zero(x)) -issignleft(o::ReverseOrdering, x::Floats) = lt(o, x, -zero(x)) +issignleft(o::ForwardOrdering, x::IEEEFloat) = lt(o, x, zero(x)) +issignleft(o::ReverseOrdering, x::IEEEFloat) = lt(o, x, -zero(x)) issignleft(o::Perm, i::Integer) = issignleft(o.order, o.data[i]) - function fpsort!(v::AbstractVector{T}, a::Algorithm, o::Ordering, t::Union{AbstractVector{T}, Nothing}=nothing) where T # fpsort!'s optimizations speed up comparisons, of which there are O(nlogn). @@ -1624,4 +1602,10 @@ end end # module Sort.Float +include("ranges.jl") + +for sym in Base.unsorted_names(Sort) + @eval Base const $sym = $(eval(sym)) +end + end # module Sort diff --git a/stdlib/Sort/src/ranges.jl b/stdlib/Sort/src/ranges.jl new file mode 100644 index 0000000000000..27f118df11443 --- /dev/null +++ b/stdlib/Sort/src/ranges.jl @@ -0,0 +1,12 @@ +# This file is a part of Julia. License is MIT: https://julialang.org/license + +issorted(r::AbstractUnitRange) = true +issorted(r::AbstractRange) = length(r) <= 1 || step(r) >= zero(step(r)) + +sort(r::AbstractUnitRange) = r +sort!(r::AbstractUnitRange) = r + +sort(r::AbstractRange) = issorted(r) ? r : reverse(r) + +sortperm(r::AbstractUnitRange) = 1:length(r) +sortperm(r::AbstractRange) = issorted(r) ? (1:1:length(r)) : (length(r):-1:1) diff --git a/test/sorting.jl b/stdlib/Sort/test/runtests.jl similarity index 99% rename from test/sorting.jl rename to stdlib/Sort/test/runtests.jl index 9766ee99ce751..48eeb6c9d24ad 100644 --- a/test/sorting.jl +++ b/stdlib/Sort/test/runtests.jl @@ -1,12 +1,11 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license -module SortingTests - using Base.Order using Random using Test -isdefined(Main, :OffsetArrays) || @eval Main include("testhelpers/OffsetArrays.jl") +const BASE_TEST_PATH = joinpath(Sys.BINDIR, "..", "share", "julia", "test") +isdefined(Main, :OffsetArrays) || @eval Main include(joinpath($(BASE_TEST_PATH), "testhelpers", "OffsetArrays.jl")) using .Main.OffsetArrays @testset "Order" begin @@ -874,5 +873,3 @@ end end end # The "searchsorted" testset is at the end of the file because it is slow. - -end diff --git a/test/choosetests.jl b/test/choosetests.jl index 95ca708b1d142..e9a3ad6c3d0b4 100644 --- a/test/choosetests.jl +++ b/test/choosetests.jl @@ -14,7 +14,7 @@ const TESTNAMES = [ "intfuncs", "simdloop", "vecelement", "rational", "bitarray", "copy", "math", "fastmath", "functional", "iterators", "operators", "ordering", "path", "ccall", "parse", "loading", "gmp", - "sorting", "spawn", "backtrace", "exceptions", + "spawn", "backtrace", "exceptions", "file", "read", "version", "namedtuple", "mpfr", "broadcast", "complex", "floatapprox", "stdlib", "reflection", "regex", "float16", @@ -142,7 +142,7 @@ function choosetests(choices = []) filtertests!(tests, "subarray") filtertests!(tests, "compiler", [ "compiler/datastructures", "compiler/inference", "compiler/effects", - "compiler/validation", "compiler/ssair", "compiler/irpasses", + "compiler/validation", "compiler/sort", "compiler/ssair", "compiler/irpasses", "compiler/codegen", "compiler/inline", "compiler/contextual", "compiler/AbstractInterpreter", "compiler/EscapeAnalysis/local", "compiler/EscapeAnalysis/interprocedural"]) diff --git a/test/compiler/sort.jl b/test/compiler/sort.jl new file mode 100644 index 0000000000000..beba0f833df5a --- /dev/null +++ b/test/compiler/sort.jl @@ -0,0 +1,44 @@ +@testset "searchsorted" begin + @test Core.Compiler.searchsorted([1, 1, 2, 2, 3, 3], 0) === Core.Compiler.UnitRange(1, 0) + @test Core.Compiler.searchsorted([1, 1, 2, 2, 3, 3], 1) === Core.Compiler.UnitRange(1, 2) + @test Core.Compiler.searchsorted([1, 1, 2, 2, 3, 3], 2) === Core.Compiler.UnitRange(3, 4) + @test Core.Compiler.searchsorted([1, 1, 2, 2, 3, 3], 4) === Core.Compiler.UnitRange(7, 6) + @test Core.Compiler.searchsorted([1, 1, 2, 2, 3, 3], 2.5; lt=<) === Core.Compiler.UnitRange(5, 4) + + @test Core.Compiler.searchsorted(Core.Compiler.UnitRange(1, 3), 0) === Core.Compiler.UnitRange(1, 0) + @test Core.Compiler.searchsorted(Core.Compiler.UnitRange(1, 3), 1) === Core.Compiler.UnitRange(1, 1) + @test Core.Compiler.searchsorted(Core.Compiler.UnitRange(1, 3), 2) === Core.Compiler.UnitRange(2, 2) + @test Core.Compiler.searchsorted(Core.Compiler.UnitRange(1, 3), 4) === Core.Compiler.UnitRange(4, 3) + + @test Core.Compiler.searchsorted([1:10;], 1, by=(x -> x >= 5)) === Core.Compiler.UnitRange(1, 4) + @test Core.Compiler.searchsorted([1:10;], 10, by=(x -> x >= 5)) === Core.Compiler.UnitRange(5, 10) + @test Core.Compiler.searchsorted([1:5; 1:5; 1:5], 1, 6, 10, Core.Compiler.Forward) === Core.Compiler.UnitRange(6, 6) + @test Core.Compiler.searchsorted(fill(1, 15), 1, 6, 10, Core.Compiler.Forward) === Core.Compiler.UnitRange(6, 10) + + for (rg,I) in Any[(Core.Compiler.UnitRange(49, 57), 47:59), + (Core.Compiler.StepRange(1, 2, 17), -1:19)] + rg_r = Core.Compiler.reverse(rg) + rgv, rgv_r = Core.Compiler.collect(rg), Core.Compiler.collect(rg_r) + for i = I + @test Core.Compiler.searchsorted(rg,i) === Core.Compiler.searchsorted(rgv,i) + @test Core.Compiler.searchsorted(rg_r,i,rev=true) === Core.Compiler.searchsorted(rgv_r,i,rev=true) + end + end +end + +@testset "basic sort" begin + v = [3,1,2] + @test v == [3,1,2] + @test Core.Compiler.sort!(v) === v == [1,2,3] + @test Core.Compiler.sort!(v, by = x -> -x) === v == [3,2,1] + @test Core.Compiler.sort!(v, by = x -> -x, < = >) === v == [1,2,3] +end + +@testset "randomized sorting tests" begin + for n in [0, 1, 3, 10, 30, 100, 300], k in [0, 30, 2n] + v = rand(-1:k, n) + for by in [identity, x -> -x, x -> x^2 + .1x], lt in [<, >] + @test sort(v; by, lt) == Core.Compiler.sort!(copy(v); by, < = lt) + end + end +end diff --git a/test/precompile.jl b/test/precompile.jl index fc73231a3e308..ba078d7d8db59 100644 --- a/test/precompile.jl +++ b/test/precompile.jl @@ -375,7 +375,7 @@ precompile_test_harness(false) do dir :Distributed, :Downloads, :FileWatching, :Future, :InteractiveUtils, :libblastrampoline_jll, :LazyArtifacts, :LibCURL, :LibCURL_jll, :LibGit2, :Libdl, :LinearAlgebra, :Logging, :Markdown, :Mmap, :MozillaCACerts_jll, :NetworkOptions, :OpenBLAS_jll, :Pkg, :Printf, - :Profile, :p7zip_jll, :REPL, :Random, :SHA, :Serialization, :SharedArrays, :Sockets, + :Profile, :p7zip_jll, :REPL, :Random, :SHA, :Serialization, :SharedArrays, :Sockets, :Sort, :SparseArrays, :TOML, :Tar, :Test, :UUIDs, :Unicode, :nghttp2_jll] ),