diff --git a/base/reflection.jl b/base/reflection.jl index cdb254e0a4c42..0c1a09068e418 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/show.jl b/base/show.jl index 57acc50f9bdea..cc82f7ecebb75 100644 --- a/base/show.jl +++ b/base/show.jl @@ -606,7 +606,7 @@ function make_typealias(@nospecialize(x::Type)) end x isa UnionAll && push!(xenv, x) for mod in mods - for name in names(mod) + for name in unsorted_names(mod) if isdefined(mod, name) && !isdeprecated(mod, name) && isconst(mod, name) alias = getfield(mod, name) if alias isa Type && !has_free_typevars(alias) && !print_without_params(alias) && x <: alias @@ -810,7 +810,7 @@ function make_typealiases(@nospecialize(x::Type)) end x isa UnionAll && push!(xenv, x) for mod in mods - for name in names(mod) + for name in unsorted_names(mod) if isdefined(mod, name) && !isdeprecated(mod, name) && isconst(mod, name) alias = getfield(mod, name) if alias isa Type && !has_free_typevars(alias) && !print_without_params(alias) && !(alias <: Tuple) diff --git a/base/sort.jl b/base/sort.jl index f6f737ac2082e..a986ac827ef6e 100644 --- a/base/sort.jl +++ b/base/sort.jl @@ -5,16 +5,16 @@ 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: >>>, !==, != +using .Base: length, first, last, axes, firstindex, lastindex, eltype, + similar, iterate, keytype, copymutable, fill, eachindex, zip, + copyto!, reverse!, resize!, require_one_based_indexing, + AbstractVector, Vector, AbstractRange, OrdinalRange, UnitRange, LinearIndices, OneTo, + identity, isless, min, max, extrema, sub_with_overflow, add_with_overflow, oneunit, + reinterpret, signed, unsigned, Signed, Unsigned, typemin, Type, BitSigned, Val, + Missing, missing, ismissing, @eval, @inbounds, @inline, @noinline, + (:), >, <, <=, >=, ==, !=, ===, |, +, -, *, !, <<, >>, &, >>>, !==, div, xor, + midpoint, @boundscheck, checkbounds, hash import .Base: sort, @@ -95,7 +95,7 @@ issorted(itr; issorted(itr, ord(lt,by,rev,order)) function partialsort!(v::AbstractVector, k::Union{Integer,OrdinalRange}, o::Ordering) - sort!(v, firstindex(v), lastindex(v), PartialQuickSort(k), o) + sort!(v, PartialQuickSort(k), o) maybeview(v, k) end @@ -422,51 +422,37 @@ insorted(x, r::AbstractRange) = in(x, r) abstract type Algorithm end struct InsertionSortAlg <: Algorithm end -struct QuickSortAlg <: Algorithm end struct MergeSortAlg <: Algorithm end +struct AdaptiveSortAlg <: Algorithm end """ - AdaptiveSort(fallback) + PartialQuickSort(lo::Union{Integer, Missing}, hi::Union{Integer, Missing}) -Indicate that a sorting function should use the fastest available algorithm. +Indicate that a sorting function should use the partial quick sort algorithm. -Adaptive sort will use the algorithm specified by `fallback` for types and orders that are -not [`UIntMappable`](@ref). Otherwise, it will typically use: - * Insertion sort for short vectors - * Radix sort for long vectors - * Counting sort for vectors of integers spanning a short range - -Adaptive sort is guaranteed to be stable if the fallback algorithm is stable. -""" -struct AdaptiveSort{Fallback <: Algorithm} <: Algorithm - fallback::Fallback -end -""" - PartialQuickSort{T <: Union{Integer,OrdinalRange}} - -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). +Partial quick sort finds and sorts the elements that would end up in positions +`lo:hi` using [`QuickSort`](@ref). 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. + * *stable*: preserves the ordering of elements which compare equal + (e.g. "a" and "A" in a sort of letters which ignores case). + * *not in-place* in memory. * *divide-and-conquer*: sort strategy similar to [`MergeSort`](@ref). """ -struct PartialQuickSort{T <: Union{Integer,OrdinalRange}} <: Algorithm - k::T +struct PartialQuickSort{L<:Union{Integer,Missing}, H<:Union{Integer,Missing}} <: Algorithm + lo::L + hi::H end - +PartialQuickSort(k::Integer) = PartialQuickSort(missing, k) +PartialQuickSort(k::OrdinalRange) = PartialQuickSort(first(k), last(k)) """ InsertionSort -Indicate that a sorting function should use the insertion sort -algorithm. Insertion sort traverses the collection one element -at a time, inserting each element into its correct, sorted position in -the output vector. +Indicate that a sorting function should use the insertion sort algorithm. + +Insertion sort traverses the collection one element at a time, inserting +each element into its correct, sorted position in the output vector. Characteristics: * *stable*: preserves the ordering of elements which @@ -477,29 +463,34 @@ Characteristics: it is well-suited to small collections but should not be used for large ones. """ const InsertionSort = InsertionSortAlg() + """ QuickSort -Indicate that a sorting function should use the quick sort -algorithm, which is *not* stable. +Indicate that a sorting function should use the quick sort algorithm. + +Quick sort picks a pivot element, partitions the array based on the pivot, +and then sorts the elements before and after the pivot recursively. 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. + * *stable*: preserves the ordering of elements which compare equal + (e.g. "a" and "A" in a sort of letters which ignores case). + * *not in-place* in memory. * *divide-and-conquer*: sort strategy similar to [`MergeSort`](@ref). - * *good performance* for large collections. + * *good performance* for almost all large collections. + * *quadratic worst case runtime* in pathological cases + (vanishingly rare for non-malicious input) """ -const QuickSort = QuickSortAlg() +const QuickSort = PartialQuickSort(missing, missing) + """ MergeSort -Indicate that a sorting function should use the merge sort -algorithm. Merge sort divides the collection into -subcollections and repeatedly merges them, sorting each -subcollection at each step, until the entire -collection has been recombined in sorted form. +Indicate that a sorting function should use the merge sort algorithm. + +Merge sort divides the collection into subcollections and +repeatedly merges them, sorting each subcollection at each step, +until the entire collection has been recombined in sorted form. Characteristics: * *stable*: preserves the ordering of elements which compare @@ -508,10 +499,23 @@ Characteristics: * *not in-place* in memory. * *divide-and-conquer* sort strategy. """ -const MergeSort = MergeSortAlg() +const MergeSort = MergeSortAlg() + +""" + AdaptiveSort + +Indicate that a sorting function should use the fastest available stable algorithm. + +Currently, AdaptiveSort uses + * [`InsertionSort`](@ref) for short vectors + * [`QuickSort`](@ref) for vectors that are not [`UIntMappable`](@ref) + * Radix sort for long vectors + * Counting sort for vectors of integers spanning a short range +""" +const AdaptiveSort = AdaptiveSortAlg() -const DEFAULT_UNSTABLE = AdaptiveSort(QuickSort) -const DEFAULT_STABLE = AdaptiveSort(MergeSort) +const DEFAULT_UNSTABLE = AdaptiveSort +const DEFAULT_STABLE = AdaptiveSort const SMALL_ALGORITHM = InsertionSort const SMALL_THRESHOLD = 20 @@ -533,75 +537,92 @@ function sort!(v::AbstractVector, lo::Integer, hi::Integer, ::InsertionSortAlg, return v end -# selectpivot! +# select a pivot for QuickSort # -# Given 3 locations in an array (lo, mi, and hi), sort v[lo], v[mi], v[hi]) and -# choose the middle value as a pivot -# -# Upon return, the pivot is in v[lo], and v[hi] is guaranteed to be -# greater than the pivot +# This method is redefined to rand(lo:hi) in Random.jl +# We can't use rand here because it is not available in Core.Compiler and +# because rand is defined in the stdlib Random.jl after sorting is used in Base. +select_pivot(lo::Integer, hi::Integer) = typeof(hi-lo)(hash(lo) % (hi-lo+1)) + lo -@inline function selectpivot!(v::AbstractVector, lo::Integer, hi::Integer, o::Ordering) +# select a pivot, partition v[lo:hi] according +# to the pivot, and store the result in t[lo:hi]. +# +# returns (pivot, pivot_index) where pivot_index is the location the pivot +# should end up, but does not set t[pivot_index] = pivot +function partition!(t::AbstractVector, lo::Integer, hi::Integer, o::Ordering, v::AbstractVector, rev::Bool) + pivot_index = select_pivot(lo, hi) + trues = 0 @inbounds begin - mi = midpoint(lo, hi) - - # sort v[mi] <= v[lo] <= v[hi] such that the pivot is immediately in place - if lt(o, v[lo], v[mi]) - v[mi], v[lo] = v[lo], v[mi] + pivot = v[pivot_index] + while lo < pivot_index + x = v[lo] + fx = rev ? !lt(o, x, pivot) : lt(o, pivot, x) + t[(fx ? hi : lo) - trues] = x + trues += fx + lo += 1 end - - if lt(o, v[hi], v[lo]) - if lt(o, v[hi], v[mi]) - v[hi], v[lo], v[mi] = v[lo], v[mi], v[hi] - else - v[hi], v[lo] = v[lo], v[hi] - end + while lo < hi + x = v[lo+1] + fx = rev ? lt(o, pivot, x) : !lt(o, x, pivot) + t[(fx ? hi : lo) - trues] = x + trues += fx + lo += 1 end - - # return the pivot - return v[lo] end -end -# partition! -# -# select a pivot, and partition v according to the pivot + # pivot_index = lo-trues + # t[pivot_index] is whatever it was before + # t[pivot_index] >* pivot, reverse stable -function partition!(v::AbstractVector, lo::Integer, hi::Integer, o::Ordering) - pivot = selectpivot!(v, lo, hi, o) - # pivot == v[lo], v[hi] > pivot - i, j = lo, hi - @inbounds while true - i += 1; j -= 1 - while lt(o, v[i], pivot); i += 1; end; - while lt(o, pivot, v[j]); j -= 1; end; - i >= j && break - v[i], v[j] = v[j], v[i] + pivot, lo-trues +end + +function sort!(v::AbstractVector, lo::Integer, hi::Integer, a::PartialQuickSort, + o::Ordering, t::AbstractVector=similar(v), swap=false, rev=false; + check_presorted=true) + + if check_presorted && !rev && !swap + # Even if we are only sorting a short region, we can only short-circuit if the whole + # vector is presorted. A weaker condition is possible, but unlikely to be useful. + if _issorted(v, lo, hi, o) + return v + elseif _issorted(v, lo, hi, Lt((x, y) -> !lt(o, x, y))) + # Reverse only if necessary. Using issorted(..., Reverse(o)) would violate stability. + return reverse!(v, lo, hi) + end end - v[j], v[lo] = pivot, v[j] - # v[j] == pivot - # v[k] >= pivot for k > j - # v[i] <= pivot for i < j - return j -end + while lo < hi && hi - lo > SMALL_THRESHOLD + pivot, j = swap ? partition!(v, lo, hi, o, t, rev) : partition!(t, lo, hi, o, v, rev) + @inbounds v[j] = pivot + swap = !swap -function sort!(v::AbstractVector, lo::Integer, hi::Integer, a::QuickSortAlg, o::Ordering) - @inbounds while lo < hi - hi-lo <= SMALL_THRESHOLD && return sort!(v, lo, hi, SMALL_ALGORITHM, o) - j = partition!(v, lo, hi, o) - if j-lo < hi-j - # recurse on the smaller chunk - # this is necessary to preserve O(log(n)) - # stack space in the worst case (rather than O(n)) - lo < (j-1) && sort!(v, lo, j-1, a, o) + # For QuickSort, a.lo === a.hi === missing, so the first two branches get skipped + if !ismissing(a.lo) && j <= a.lo # Skip sorting the lower part + swap && copyto!(v, lo, t, lo, j-lo) + rev && reverse!(v, lo, j-1) lo = j+1 - else - j+1 < hi && sort!(v, j+1, hi, a, o) + rev = !rev + elseif !ismissing(a.hi) && a.hi <= j # Skip sorting the upper part + swap && copyto!(v, j+1, t, j+1, hi-j) + rev || reverse!(v, j+1, hi) + hi = j-1 + elseif j-lo < hi-j + # Sort the lower part recursively because it is smaller. Recursing on the + # smaller part guarantees O(log(n)) stack space even on pathological inputs. + sort!(v, lo, j-1, a, o, t, swap, rev; check_presorted=false) + lo = j+1 + rev = !rev + else # Sort the higher part recursively + sort!(v, j+1, hi, a, o, t, swap, !rev; check_presorted=false) hi = j-1 end end - return v + hi < lo && return v + swap && copyto!(v, lo, t, lo, hi-lo+1) + rev && reverse!(v, lo, hi) + sort!(v, lo, hi, SMALL_ALGORITHM, o) end function sort!(v::AbstractVector{T}, lo::Integer, hi::Integer, a::MergeSortAlg, o::Ordering, @@ -646,32 +667,6 @@ function sort!(v::AbstractVector{T}, lo::Integer, hi::Integer, a::MergeSortAlg, return v end -function sort!(v::AbstractVector, lo::Integer, hi::Integer, a::PartialQuickSort, - o::Ordering) - @inbounds while lo < hi - hi-lo <= SMALL_THRESHOLD && return sort!(v, lo, hi, SMALL_ALGORITHM, o) - j = partition!(v, lo, hi, o) - - if j <= first(a.k) - lo = j+1 - elseif j >= last(a.k) - hi = j-1 - else - # recurse on the smaller chunk - # this is necessary to preserve O(log(n)) - # stack space in the worst case (rather than O(n)) - if j-lo < hi-j - lo < (j-1) && sort!(v, lo, j-1, a, o) - lo = j+1 - else - hi > (j+1) && sort!(v, j+1, hi, a, o) - hi = j-1 - end - end - end - return v -end - # This is a stable least significant bit first radix sort. # # That is, it first sorts the entire vector by the last chunk_size bits, then by the second @@ -741,7 +736,7 @@ end # For AbstractVector{Bool}, counting sort is always best. # This is an implementation of counting sort specialized for Bools. # Accepts unused buffer to avoid method ambiguity. -function sort!(v::AbstractVector{Bool}, lo::Integer, hi::Integer, a::AdaptiveSort, o::Ordering, +function sort!(v::AbstractVector{Bool}, lo::Integer, hi::Integer, ::AdaptiveSortAlg, o::Ordering, t::Union{AbstractVector{Bool}, Nothing}=nothing) first = lt(o, false, true) ? false : lt(o, true, false) ? true : return v count = 0 @@ -773,12 +768,12 @@ function _issorted(v::AbstractVector, lo::Integer, hi::Integer, o::Ordering) end true end -function sort!(v::AbstractVector{T}, lo::Integer, hi::Integer, a::AdaptiveSort, o::Ordering, - t::Union{AbstractVector{T}, Nothing}=nothing) where T +function sort!(v::AbstractVector{T}, lo::Integer, hi::Integer, ::AdaptiveSortAlg, o::Ordering, + t::Union{AbstractVector{T}, Nothing}=nothing) where T # if the sorting task is not UIntMappable, then we can't radix sort or sort_int_range! # so we skip straight to the fallback algorithm which is comparison based. - U = UIntMappable(T, o) - U === nothing && return sort!(v, lo, hi, a.fallback, o) + U = UIntMappable(eltype(v), o) + U === nothing && return sort!(v, lo, hi, QuickSort, o) # to avoid introducing excessive detection costs for the trivial sorting problem # and to avoid overflow, we check for small inputs before any other runtime checks @@ -795,6 +790,8 @@ function sort!(v::AbstractVector{T}, lo::Integer, hi::Integer, a::AdaptiveSort, # For large arrays, a reverse-sorted check is essentially free (overhead < 1%) if lenm1 >= 500 && _issorted(v, lo, hi, ReverseOrdering(o)) + # If reversing is valid, do so. This does not violate stability + # because being UIntMappable implies a linear order. reverse!(v, lo, hi) return v end @@ -813,7 +810,7 @@ function sort!(v::AbstractVector{T}, lo::Integer, hi::Integer, a::AdaptiveSort, return sort_int_range!(v, Int(v_range+1), v_min, o === Forward ? identity : reverse, lo, hi) end end - return sort!(v, lo, hi, a.fallback, o) + return sort!(v, lo, hi, QuickSort, o; check_presorted=false) end v_min, v_max = _extrema(v, lo, hi, o) @@ -839,17 +836,15 @@ function sort!(v::AbstractVector{T}, lo::Integer, hi::Integer, a::AdaptiveSort, # where we only need to radix over the last few bits (5, in the example). bits = unsigned(8sizeof(u_range) - leading_zeros(u_range)) - # radix sort runs in O(bits * lenm1), insertion sort runs in O(lenm1^2). Radix sort - # has a constant factor that is three times higher, so radix runtime is 3bits * lenm1 - # and insertion runtime is lenm1^2. Empirically, insertion is faster than radix iff - # lenm1 < 3bits. - # Insertion < Radix - # lenm1^2 < 3 * bits * lenm1 - # lenm1 < 3bits - if lenm1 < 3bits - # at lenm1 = 64*3-1, QuickSort is about 20% faster than InsertionSort. - alg = a.fallback === QuickSort && lenm1 > 120 ? QuickSort : SMALL_ALGORITHM - return sort!(v, lo, hi, alg, o) + # radix sort runs in O(bits * lenm1), quick sort runs in O(lenm1 * log(lenm1)). + # dividing both sides by lenm1 and introducing empirical constant factors yields + # the following heuristic for when QuickSort is faster than RadixSort + if 22log(lenm1) < bits + 70 + return if lenm1 > 80 + sort!(v, lo, hi, QuickSort, o; check_presorted=false) + else + sort!(v, lo, hi, SMALL_ALGORITHM, o) + end end # At this point, we are committed to radix sort. @@ -891,12 +886,12 @@ defalg(v::AbstractArray{Missing}) = DEFAULT_UNSTABLE # for method disambiguation defalg(v::AbstractArray{Union{}}) = DEFAULT_UNSTABLE # for method disambiguation function sort!(v::AbstractVector{T}, alg::Algorithm, - order::Ordering, t::Union{AbstractVector{T}, Nothing}=nothing) where T + order::Ordering, t::Union{AbstractVector{T}, Nothing}=nothing) where T sort!(v, firstindex(v), lastindex(v), alg, order, t) end function sort!(v::AbstractVector{T}, lo::Integer, hi::Integer, alg::Algorithm, - order::Ordering, t::Union{AbstractVector{T}, Nothing}=nothing) where T + order::Ordering, t::Union{AbstractVector{T}, Nothing}=nothing) where T sort!(v, lo, hi, alg, order) end @@ -1591,7 +1586,7 @@ issignleft(o::ReverseOrdering, x::Floats) = 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 + t::Union{AbstractVector{T}, Nothing}=nothing) where T # fpsort!'s optimizations speed up comparisons, of which there are O(nlogn). # The overhead is O(n). For n < 10, it's not worth it. length(v) < 10 && return sort!(v, firstindex(v), lastindex(v), SMALL_ALGORITHM, o, t) @@ -1610,15 +1605,12 @@ function fpsort!(v::AbstractVector{T}, a::Algorithm, o::Ordering, end -fpsort!(v::AbstractVector, a::Sort.PartialQuickSort, o::Ordering) = - sort!(v, firstindex(v), lastindex(v), a, o) - function sort!(v::FPSortable, a::Algorithm, o::DirectOrdering, - t::Union{FPSortable, Nothing}=nothing) + t::Union{FPSortable, Nothing}=nothing) fpsort!(v, a, o, t) end function sort!(v::AbstractVector{T}, a::Algorithm, o::Perm{<:DirectOrdering,<:FPSortable}, - t::Union{AbstractVector{T}, Nothing}=nothing) where T <: Union{Signed, Unsigned} + t::Union{AbstractVector{T}, Nothing}=nothing) where T <: Union{Signed, Unsigned} fpsort!(v, a, o, t) end diff --git a/base/sysimg.jl b/base/sysimg.jl index 5a14bf5bfd3b9..ef7bad929b743 100644 --- a/base/sysimg.jl +++ b/base/sysimg.jl @@ -19,6 +19,14 @@ Base.init_load_path() if Base.is_primary_base_module # load some stdlib packages but don't put their names in Main let + # Loading here does not call __init__(). This leads to uninitialized RNG + # state which causes rand(::UnitRange{Int}) to hang. This is a workaround: + task = current_task() + task.rngState0 = 0x5156087469e170ab + task.rngState1 = 0x7431eaead385992c + task.rngState2 = 0x503e1d32781c2608 + task.rngState3 = 0x3a77f7189200c20b + # Stdlibs sorted in dependency, then alphabetical, order by contrib/print_sorted_stdlibs.jl # Run with the `--exclude-jlls` option to filter out all JLL packages stdlibs = [ diff --git a/contrib/generate_precompile.jl b/contrib/generate_precompile.jl index 264036f4fb3ae..63e0a86a94d26 100644 --- a/contrib/generate_precompile.jl +++ b/contrib/generate_precompile.jl @@ -372,9 +372,9 @@ function generate_precompile_statements() end end - # Execute the collected precompile statements + # Execute the precompile statements n_succeeded = 0 - include_time = @elapsed for statement in sort!(collect(statements)) + include_time = @elapsed for statement in statements # println(statement) # XXX: skip some that are broken. these are caused by issue #39902 occursin("Tuple{Artifacts.var\"#@artifact_str\", LineNumberNode, Module, Any, Any}", statement) && continue diff --git a/stdlib/Random/src/Random.jl b/stdlib/Random/src/Random.jl index b9adb5ae39f54..95125422eeee5 100644 --- a/stdlib/Random/src/Random.jl +++ b/stdlib/Random/src/Random.jl @@ -434,4 +434,10 @@ true """ seed!(rng::AbstractRNG, ::Nothing) = seed!(rng) +# Randomize quicksort pivot selection. This code is here because of bootstrapping: +# we need to sort things before we load this standard library. +# TODO move this into Sort.jl +Base.delete_method(only(methods(Base.Sort.select_pivot))) +Base.Sort.select_pivot(lo::Integer, hi::Integer) = rand(lo:hi) + end # module diff --git a/test/sorting.jl b/test/sorting.jl index 9766ee99ce751..acb628406581e 100644 --- a/test/sorting.jl +++ b/test/sorting.jl @@ -78,6 +78,14 @@ end @test sort(Union{}[]) == Union{}[] # issue #45280 end +@testset "stability" begin + for Alg in [InsertionSort, MergeSort, QuickSort, Base.Sort.AdaptiveSort, Base.DEFAULT_STABLE, + PartialQuickSort(missing, 1729), PartialQuickSort(1729, missing)] + @test issorted(sort(1:2000, alg=Alg, by=x->0)) + @test issorted(sort(1:2000, alg=Alg, by=x->x÷100)) + end +end + @testset "partialsort" begin @test partialsort([3,6,30,1,9],3) == 6 @test partialsort([3,6,30,1,9],3:4) == [6,9] @@ -120,9 +128,11 @@ Base.step(r::ConstantRange) = 0 @test searchsortedlast(r, 1.0, Forward) == 5 @test searchsortedlast(r, 1, Forward) == 5 @test searchsortedlast(r, UInt(1), Forward) == 5 +end +@testset "Each sorting algorithm individually" begin a = rand(1:10000, 1000) - for alg in [InsertionSort, MergeSort, Base.DEFAULT_STABLE] + for alg in [InsertionSort, MergeSort, QuickSort, Base.DEFAULT_STABLE, Base.DEFAULT_UNSTABLE] b = sort(a, alg=alg) @test issorted(b) @@ -187,18 +197,16 @@ Base.step(r::ConstantRange) = 0 @test b == c end - @testset "unstable algorithms" begin - for alg in [QuickSort, Base.DEFAULT_UNSTABLE] - b = sort(a, alg=alg) - @test issorted(b) - @test last(b) == last(sort(a, alg=PartialQuickSort(length(a)))) - b = sort(a, alg=alg, rev=true) - @test issorted(b, rev=true) - @test last(b) == last(sort(a, alg=PartialQuickSort(length(a)), rev=true)) - b = sort(a, alg=alg, by=x->1/x) - @test issorted(b, by=x->1/x) - @test last(b) == last(sort(a, alg=PartialQuickSort(length(a)), by=x->1/x)) - end + @testset "PartialQuickSort" begin + b = sort(a) + @test issorted(b) + @test last(b) == last(sort(a, alg=PartialQuickSort(length(a)))) + b = sort(a, rev=true) + @test issorted(b, rev=true) + @test last(b) == last(sort(a, alg=PartialQuickSort(length(a)), rev=true)) + b = sort(a, by=x->1/x) + @test issorted(b, by=x->1/x) + @test last(b) == last(sort(a, alg=PartialQuickSort(length(a)), by=x->1/x)) end end @testset "insorted" begin @@ -259,8 +267,8 @@ end @testset "PartialQuickSort" begin a = rand(1:10000, 1000) # test PartialQuickSort only does a partial sort - let alg = PartialQuickSort(1:div(length(a), 10)) - k = alg.k + let k = 1:div(length(a), 10) + alg = PartialQuickSort(k) b = sort(a, alg=alg) c = sort(a, alg=alg, by=x->1/x) d = sort(a, alg=alg, rev=true) @@ -271,8 +279,8 @@ end @test !issorted(c, by=x->1/x) @test !issorted(d, rev=true) end - let alg = PartialQuickSort(div(length(a), 10)) - k = alg.k + let k = div(length(a), 10) + alg = PartialQuickSort(k) b = sort(a, alg=alg) c = sort(a, alg=alg, by=x->1/x) d = sort(a, alg=alg, rev=true) @@ -289,6 +297,7 @@ end @test partialsortperm([3,6,30,1,9], 2, rev=true) == 5 @test partialsortperm([3,6,30,1,9], 2, by=x->1/x) == 5 end + ## more advanced sorting tests ## randnans(n) = reinterpret(Float64,[rand(UInt64)|0x7ff8000000000000 for i=1:n]) @@ -324,7 +333,7 @@ end @test c == v # stable algorithms - for alg in [MergeSort, Base.DEFAULT_STABLE] + for alg in [MergeSort, QuickSort, PartialQuickSort(1:n), Base.DEFAULT_STABLE] p = sortperm(v, alg=alg, rev=rev) p2 = sortperm(float(v), alg=alg, rev=rev) @test p == p2 @@ -334,6 +343,10 @@ end @test s == si invpermute!(s, p) @test s == v + + # Ensure stability, even with reverse short circuit + @test all(sort!(Real[fill(2.0, 15); fill(2, 15); fill(1.0, 15); fill(1, 15)]) + .=== Real[fill(1.0, 15); fill(1, 15); fill(2.0, 15); fill(2, 15)]) end # unstable algorithms @@ -368,8 +381,7 @@ end end v = randn_with_nans(n,0.1) - # TODO: alg = PartialQuickSort(n) fails here - for alg in [InsertionSort, QuickSort, MergeSort, Base.DEFAULT_UNSTABLE, Base.DEFAULT_STABLE], + for alg in [InsertionSort, MergeSort, QuickSort, PartialQuickSort(n), Base.DEFAULT_UNSTABLE, Base.DEFAULT_STABLE], rev in [false,true] alg === InsertionSort && n >= 3000 && continue # test float sorting with NaNs @@ -431,7 +443,7 @@ end @test all(issorted, [sp[inds.==x] for x in 1:200]) end - for alg in [InsertionSort, MergeSort, Base.DEFAULT_STABLE] + for alg in [InsertionSort, MergeSort, QuickSort, Base.DEFAULT_STABLE] sp = sortperm(inds, alg=alg) @test all(issorted, [sp[inds.==x] for x in 1:200]) end @@ -682,6 +694,52 @@ end @test Base.Sort.UIntMappable(Union{Int, UInt}, Base.Forward) === nothing # issue #45280 end +@testset "invalid lt (#11429)" begin + # lt must be a total linear order (e.g. < not <=) so this usage is + # not allowed. Consequently, none of the behavior tested in this + # testset is gaurunteed to work in future minor versions of Julia. + + n = 1000 + v = rand(1:5, n); + s = sort(v); + + # Nevertheless, it still works... + for alg in [InsertionSort, MergeSort, QuickSort, + Base.Sort.AdaptiveSort, Base.DEFAULT_STABLE, Base.DEFAULT_UNSTABLE] + @test sort(v, alg=alg, lt = <=) == s + end + @test partialsort(v, 172, lt = <=) == s[172] + @test partialsort(v, 315:415, lt = <=) == s[315:415] + + # ...and it is consistantly reverse stable. All these algorithms swap v[i] and v[j] + # where i < j if and only if lt(o, v[j], v[i]). This invariant holds even for + # this invalid lt order. + perm = reverse(sortperm(v, rev=true)) + for alg in [InsertionSort, MergeSort, QuickSort, + Base.Sort.AdaptiveSort, Base.DEFAULT_STABLE, Base.DEFAULT_UNSTABLE] + @test sort(1:n, alg=alg, lt = (i,j) -> v[i]<=v[j]) == perm + end + @test partialsort(1:n, 172, lt = (i,j) -> v[i]<=v[j]) == perm[172] + @test partialsort(1:n, 315:415, lt = (i,j) -> v[i]<=v[j]) == perm[315:415] + + # lt can be very poorly behaved and sort will still permute its input in some way. + for alg in [InsertionSort, MergeSort, QuickSort, + Base.Sort.AdaptiveSort, Base.DEFAULT_STABLE, Base.DEFAULT_UNSTABLE] + @test sort!(sort(v, alg=alg, lt = (x,y) -> rand([false, true]))) == s + end + @test partialsort(v, 172, lt = (x,y) -> rand([false, true])) ∈ 1:5 + @test all(partialsort(v, 315:415, lt = (x,y) -> rand([false, true])) .∈ (1:5,)) + + # issue #32675 + k = [38, 18, 38, 38, 3, 37, 26, 26, 6, 29, 38, 36, 38, 1, 38, 36, 38, 38, 38, 36, 36, + 36, 28, 34, 35, 38, 25, 20, 38, 1, 1, 5, 38, 38, 3, 34, 16, 38, 4, 10, 35, 37, 38, + 38, 2, 38, 25, 35, 38, 1, 35, 36, 20, 33, 36, 18, 38, 1, 24, 4, 38, 18, 12, 38, 34, + 35, 36, 38, 26, 31, 36, 38, 38, 30, 36, 35, 35, 7, 22, 35, 38, 35, 30, 21, 37] + idx = sortperm(k; lt=!isless) + @test issorted(k[idx], rev=true) +end + +# This testset is at the end of the file because it is slow @testset "sort(x; buffer)" begin for n in [1,10,100,1000] v = rand(n)