From 84bf67c8a25d15fd9f05f85918617794bc5c154f Mon Sep 17 00:00:00 2001 From: Lilith Orion Hafner Date: Tue, 30 May 2023 14:55:28 -0500 Subject: [PATCH] Support sorting iterators (#46104) * widen sort's type signature * throw on AbstractString * Throw on infinite iterator * make sort(::NTuple) return a tuple (use vector internally for sorting for large tuples) --- base/sort.jl | 42 ++++++++++++++++++++++++++++++++++++++++-- test/sorting.jl | 31 +++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 2 deletions(-) diff --git a/base/sort.jl b/base/sort.jl index 0e84657fc481e..b78f773ad9f72 100644 --- a/base/sort.jl +++ b/base/sort.jl @@ -5,7 +5,8 @@ module Sort using Base.Order using Base: copymutable, midpoint, require_one_based_indexing, uinttype, - sub_with_overflow, add_with_overflow, OneTo, BitSigned, BitIntegerType, top_set_bit + sub_with_overflow, add_with_overflow, OneTo, BitSigned, BitIntegerType, top_set_bit, + IteratorSize, HasShape, IsInfinite, tail import Base: sort, @@ -1383,6 +1384,11 @@ end Variant of [`sort!`](@ref) that returns a sorted copy of `v` leaving `v` itself unmodified. +Uses `Base.copymutable` to support immutable collections and iterables. + +!!! compat "Julia 1.10" + `sort` of arbitrary iterables requires at least Julia 1.10. + # Examples ```jldoctest julia> v = [3, 1, 2]; @@ -1400,7 +1406,39 @@ julia> v 2 ``` """ -sort(v::AbstractVector; kws...) = sort!(copymutable(v); kws...) +function sort(v; kws...) + size = IteratorSize(v) + size == HasShape{0}() && throw(ArgumentError("$v cannot be sorted")) + size == IsInfinite() && throw(ArgumentError("infinite iterator $v cannot be sorted")) + sort!(copymutable(v); kws...) +end +sort(v::AbstractVector; kws...) = sort!(copymutable(v); kws...) # for method disambiguation +sort(::AbstractString; kws...) = + throw(ArgumentError("sort(::AbstractString) is not supported")) +sort(::Tuple; kws...) = + throw(ArgumentError("sort(::Tuple) is only supported for NTuples")) + +function sort(x::NTuple{N}; lt::Function=isless, by::Function=identity, + rev::Union{Bool,Nothing}=nothing, order::Ordering=Forward) where N + o = ord(lt,by,rev,order) + if N > 9 + v = sort!(copymutable(x), DEFAULT_STABLE, o) + tuple((v[i] for i in 1:N)...) + else + _sort(x, o) + end +end +_sort(x::Union{NTuple{0}, NTuple{1}}, o::Ordering) = x +function _sort(x::NTuple, o::Ordering) + a, b = Base.IteratorsMD.split(x, Val(length(x)>>1)) + merge(_sort(a, o), _sort(b, o), o) +end +merge(x::NTuple, y::NTuple{0}, o::Ordering) = x +merge(x::NTuple{0}, y::NTuple, o::Ordering) = y +merge(x::NTuple{0}, y::NTuple{0}, o::Ordering) = x # Method ambiguity +merge(x::NTuple, y::NTuple, o::Ordering) = + (lt(o, y[1], x[1]) ? (y[1], merge(x, tail(y), o)...) : (x[1], merge(tail(x), y, o)...)) + ## partialsortperm: the permutation to sort the first k elements of an array ## diff --git a/test/sorting.jl b/test/sorting.jl index ec1666dabb2fb..0528d9d81c296 100644 --- a/test/sorting.jl +++ b/test/sorting.jl @@ -88,6 +88,20 @@ end vcat(2000, (x:x+99 for x in 1900:-100:100)..., 1:99) end +function tuple_sort_test(x) + @test issorted(sort(x)) + length(x) > 9 && return # length > 9 uses a vector fallback + @test 0 == @allocated sort(x) +end +@testset "sort(::NTuple)" begin + @test sort((9,8,3,3,6,2,0,8)) == (0,2,3,3,6,8,8,9) + @test sort((9,8,3,3,6,2,0,8), by=x->x÷3) == (2,0,3,3,8,6,8,9) + for i in 1:40 + tuple_sort_test(tuple(rand(i)...)) + end + @test_throws ArgumentError sort((1,2,3.0)) +end + @testset "partialsort" begin @test partialsort([3,6,30,1,9],3) == 6 @test partialsort([3,6,30,1,9],3:4) == [6,9] @@ -530,6 +544,23 @@ end @test isequal(a, [8,6,7,NaN,5,3,0,9]) end +@testset "sort!(iterable)" begin + gen = (x % 7 + 0.1x for x in 1:50) + @test sort(gen) == sort!(collect(gen)) + gen = (x % 7 + 0.1y for x in 1:10, y in 1:5) + @test sort(gen; dims=1) == sort!(collect(gen); dims=1) + @test sort(gen; dims=2) == sort!(collect(gen); dims=2) + + @test_throws ArgumentError("dimension out of range") sort(gen; dims=3) + + @test_throws UndefKeywordError(:dims) sort(gen) + @test_throws UndefKeywordError(:dims) sort(collect(gen)) + @test_throws UndefKeywordError(:dims) sort!(collect(gen)) + + @test_throws ArgumentError sort("string") + @test_throws ArgumentError("1 cannot be sorted") sort(1) +end + @testset "sort!(::AbstractVector{<:Integer}) with short int range" begin a = view([9:-1:0;], :)::SubArray sort!(a)