Skip to content

Commit 84bf67c

Browse files
authored
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)
1 parent d912d85 commit 84bf67c

File tree

2 files changed

+71
-2
lines changed

2 files changed

+71
-2
lines changed

base/sort.jl

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ module Sort
55
using Base.Order
66

77
using Base: copymutable, midpoint, require_one_based_indexing, uinttype,
8-
sub_with_overflow, add_with_overflow, OneTo, BitSigned, BitIntegerType, top_set_bit
8+
sub_with_overflow, add_with_overflow, OneTo, BitSigned, BitIntegerType, top_set_bit,
9+
IteratorSize, HasShape, IsInfinite, tail
910

1011
import Base:
1112
sort,
@@ -1383,6 +1384,11 @@ end
13831384
13841385
Variant of [`sort!`](@ref) that returns a sorted copy of `v` leaving `v` itself unmodified.
13851386
1387+
Uses `Base.copymutable` to support immutable collections and iterables.
1388+
1389+
!!! compat "Julia 1.10"
1390+
`sort` of arbitrary iterables requires at least Julia 1.10.
1391+
13861392
# Examples
13871393
```jldoctest
13881394
julia> v = [3, 1, 2];
@@ -1400,7 +1406,39 @@ julia> v
14001406
2
14011407
```
14021408
"""
1403-
sort(v::AbstractVector; kws...) = sort!(copymutable(v); kws...)
1409+
function sort(v; kws...)
1410+
size = IteratorSize(v)
1411+
size == HasShape{0}() && throw(ArgumentError("$v cannot be sorted"))
1412+
size == IsInfinite() && throw(ArgumentError("infinite iterator $v cannot be sorted"))
1413+
sort!(copymutable(v); kws...)
1414+
end
1415+
sort(v::AbstractVector; kws...) = sort!(copymutable(v); kws...) # for method disambiguation
1416+
sort(::AbstractString; kws...) =
1417+
throw(ArgumentError("sort(::AbstractString) is not supported"))
1418+
sort(::Tuple; kws...) =
1419+
throw(ArgumentError("sort(::Tuple) is only supported for NTuples"))
1420+
1421+
function sort(x::NTuple{N}; lt::Function=isless, by::Function=identity,
1422+
rev::Union{Bool,Nothing}=nothing, order::Ordering=Forward) where N
1423+
o = ord(lt,by,rev,order)
1424+
if N > 9
1425+
v = sort!(copymutable(x), DEFAULT_STABLE, o)
1426+
tuple((v[i] for i in 1:N)...)
1427+
else
1428+
_sort(x, o)
1429+
end
1430+
end
1431+
_sort(x::Union{NTuple{0}, NTuple{1}}, o::Ordering) = x
1432+
function _sort(x::NTuple, o::Ordering)
1433+
a, b = Base.IteratorsMD.split(x, Val(length(x)>>1))
1434+
merge(_sort(a, o), _sort(b, o), o)
1435+
end
1436+
merge(x::NTuple, y::NTuple{0}, o::Ordering) = x
1437+
merge(x::NTuple{0}, y::NTuple, o::Ordering) = y
1438+
merge(x::NTuple{0}, y::NTuple{0}, o::Ordering) = x # Method ambiguity
1439+
merge(x::NTuple, y::NTuple, o::Ordering) =
1440+
(lt(o, y[1], x[1]) ? (y[1], merge(x, tail(y), o)...) : (x[1], merge(tail(x), y, o)...))
1441+
14041442

14051443
## partialsortperm: the permutation to sort the first k elements of an array ##
14061444

test/sorting.jl

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,20 @@ end
8888
vcat(2000, (x:x+99 for x in 1900:-100:100)..., 1:99)
8989
end
9090

91+
function tuple_sort_test(x)
92+
@test issorted(sort(x))
93+
length(x) > 9 && return # length > 9 uses a vector fallback
94+
@test 0 == @allocated sort(x)
95+
end
96+
@testset "sort(::NTuple)" begin
97+
@test sort((9,8,3,3,6,2,0,8)) == (0,2,3,3,6,8,8,9)
98+
@test sort((9,8,3,3,6,2,0,8), by=x->x÷3) == (2,0,3,3,8,6,8,9)
99+
for i in 1:40
100+
tuple_sort_test(tuple(rand(i)...))
101+
end
102+
@test_throws ArgumentError sort((1,2,3.0))
103+
end
104+
91105
@testset "partialsort" begin
92106
@test partialsort([3,6,30,1,9],3) == 6
93107
@test partialsort([3,6,30,1,9],3:4) == [6,9]
@@ -530,6 +544,23 @@ end
530544
@test isequal(a, [8,6,7,NaN,5,3,0,9])
531545
end
532546

547+
@testset "sort!(iterable)" begin
548+
gen = (x % 7 + 0.1x for x in 1:50)
549+
@test sort(gen) == sort!(collect(gen))
550+
gen = (x % 7 + 0.1y for x in 1:10, y in 1:5)
551+
@test sort(gen; dims=1) == sort!(collect(gen); dims=1)
552+
@test sort(gen; dims=2) == sort!(collect(gen); dims=2)
553+
554+
@test_throws ArgumentError("dimension out of range") sort(gen; dims=3)
555+
556+
@test_throws UndefKeywordError(:dims) sort(gen)
557+
@test_throws UndefKeywordError(:dims) sort(collect(gen))
558+
@test_throws UndefKeywordError(:dims) sort!(collect(gen))
559+
560+
@test_throws ArgumentError sort("string")
561+
@test_throws ArgumentError("1 cannot be sorted") sort(1)
562+
end
563+
533564
@testset "sort!(::AbstractVector{<:Integer}) with short int range" begin
534565
a = view([9:-1:0;], :)::SubArray
535566
sort!(a)

0 commit comments

Comments
 (0)