Skip to content

Commit ab1fcac

Browse files
tkluckKristofferC
authored andcommitted
Base.Ordering: do not ever discard order parameter (#34719)
Prior to this commit, the `order` parameter was discarded if either `by` or `order` was passed. However, there is a sane interpretation for most of these combinations, and we should use that interpretation for least surprise. The one case that doesn't have an obvious interpretation is when a non-trivial `order` is passed together with an `lt` function; in that case it is better to error out rather than let it pass silently.
1 parent e654781 commit ab1fcac

File tree

5 files changed

+80
-13
lines changed

5 files changed

+80
-13
lines changed

NEWS.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,11 @@ Standard library changes
6666
------------------------
6767
* The `@timed` macro now returns a `NamedTuple` ([#34149])
6868
* New `supertypes(T)` function returns a tuple of all supertypes of `T` ([#34419]).
69+
* Sorting-related functions such as `sort` that take the keyword arguments `lt`, `rev`, `order`
70+
and `by` now do not discard `order` if `by` or `lt` are passed. In the former case, the
71+
order from `order` is used to compare the values of `by(element)`. In the latter case,
72+
any order different from `Forward` or `Reverse` will raise an error about the
73+
ambiguity.
6974

7075
#### LinearAlgebra
7176
* The BLAS submodule now supports the level-2 BLAS subroutine `hpmv!` ([#34211]).

base/compiler/ssair/ir.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -608,7 +608,7 @@ function count_added_node!(compact::IncrementalCompact, @nospecialize(v))
608608
end
609609

610610
function resort_pending!(compact)
611-
sort!(compact.pending_perm, DEFAULT_STABLE, Order.By(x->compact.pending_nodes[x].pos))
611+
sort!(compact.pending_perm, DEFAULT_STABLE, Order.By(x->compact.pending_nodes[x].pos, Order.Forward))
612612
end
613613

614614
function insert_node!(compact::IncrementalCompact, before, @nospecialize(typ), @nospecialize(val), attach_after::Bool=false)

base/ordering.jl

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,14 @@ const DirectOrdering = Union{ForwardOrdering,ReverseOrdering{ForwardOrdering}}
3434
const Forward = ForwardOrdering()
3535
const Reverse = ReverseOrdering()
3636

37-
struct By{T} <: Ordering
37+
struct By{T, O} <: Ordering
3838
by::T
39+
order::O
3940
end
4041

42+
# backwards compatibility with VERSION < v"1.5-"
43+
By(by) = By(by, Forward)
44+
4145
struct Lt{T} <: Ordering
4246
lt::T
4347
end
@@ -47,9 +51,12 @@ struct Perm{O<:Ordering,V<:AbstractVector} <: Ordering
4751
data::V
4852
end
4953

54+
ReverseOrdering(by::By) = By(by.by, ReverseOrdering(by.order))
55+
ReverseOrdering(perm::Perm) = Perm(ReverseOrdering(perm.order), perm.data)
56+
5057
lt(o::ForwardOrdering, a, b) = isless(a,b)
5158
lt(o::ReverseOrdering, a, b) = lt(o.fwd,b,a)
52-
lt(o::By, a, b) = isless(o.by(a),o.by(b))
59+
lt(o::By, a, b) = lt(o.order,o.by(a),o.by(b))
5360
lt(o::Lt, a, b) = o.lt(a,b)
5461

5562
@propagate_inbounds function lt(p::Perm, a::Integer, b::Integer)
@@ -58,16 +65,18 @@ lt(o::Lt, a, b) = o.lt(a,b)
5865
lt(p.order, da, db) | (!lt(p.order, db, da) & (a < b))
5966
end
6067

61-
ordtype(o::ReverseOrdering, vs::AbstractArray) = ordtype(o.fwd, vs)
62-
ordtype(o::Perm, vs::AbstractArray) = ordtype(o.order, o.data)
63-
# TODO: here, we really want the return type of o.by, without calling it
64-
ordtype(o::By, vs::AbstractArray) = try typeof(o.by(vs[1])) catch; Any end
65-
ordtype(o::Ordering, vs::AbstractArray) = eltype(vs)
66-
6768
_ord(lt::typeof(isless), by::typeof(identity), order::Ordering) = order
68-
_ord(lt::typeof(isless), by, order::Ordering) = By(by)
69-
_ord(lt, by::typeof(identity), order::Ordering) = Lt(lt)
70-
_ord(lt, by, order::Ordering) = Lt((x,y)->lt(by(x),by(y)))
69+
_ord(lt::typeof(isless), by, order::Ordering) = By(by, order)
70+
71+
function _ord(lt, by, order::Ordering)
72+
if order === Forward
73+
return Lt((x, y) -> lt(by(x), by(y)))
74+
elseif order === Reverse
75+
return Lt((x, y) -> lt(by(y), by(x)))
76+
else
77+
error("Passing both lt= and order= arguments is ambiguous; please pass order=Forward or order=Reverse (or leave default)")
78+
end
79+
end
7180

7281
ord(lt, by, rev::Nothing, order::Ordering=Forward) = _ord(lt, by, order)
7382

@@ -76,4 +85,19 @@ function ord(lt, by, rev::Bool, order::Ordering=Forward)
7685
return rev ? ReverseOrdering(o) : o
7786
end
7887

88+
89+
# This function is not in use anywhere in Base but we observed
90+
# use in sorting-related packages (#34719). It's probably best to move
91+
# this functionality to those packages in the future; let's remind/force
92+
# ourselves to deprecate this in v2.0.
93+
# The following clause means `if VERSION < v"2.0-"` but it also works during
94+
# bootstrap. For the same reason, we need to write `Int32` instead of `Cint`.
95+
if ccall(:jl_ver_major, Int32, ()) < 2
96+
ordtype(o::ReverseOrdering, vs::AbstractArray) = ordtype(o.fwd, vs)
97+
ordtype(o::Perm, vs::AbstractArray) = ordtype(o.order, o.data)
98+
# TODO: here, we really want the return type of o.by, without calling it
99+
ordtype(o::By, vs::AbstractArray) = try typeof(o.by(vs[1])) catch; Any end
100+
ordtype(o::Ordering, vs::AbstractArray) = eltype(vs)
101+
end
102+
79103
end

test/choosetests.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ function choosetests(choices = [])
4040
"arrayops", "tuple", "reduce", "reducedim", "abstractarray",
4141
"intfuncs", "simdloop", "vecelement", "rational",
4242
"bitarray", "copy", "math", "fastmath", "functional", "iterators",
43-
"operators", "path", "ccall", "parse", "loading", "gmp",
43+
"operators", "ordering", "path", "ccall", "parse", "loading", "gmp",
4444
"sorting", "spawn", "backtrace", "exceptions",
4545
"file", "read", "version", "namedtuple",
4646
"mpfr", "broadcast", "complex",

test/ordering.jl

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
using Test
2+
3+
import Base.Order: Forward, Reverse
4+
5+
# every argument can flip the integer order by passing the right value. Here,
6+
# we enumerate a few of these combinations and check that all these flips
7+
# compound so that in total we either have an increasing or decreasing sort.
8+
for (s1, rev) in enumerate([true, false])
9+
for (s2, lt) in enumerate([>, <, (a, b) -> a - b > 0, (a, b) -> a - b < 0])
10+
for (s3, by) in enumerate([-, +])
11+
for (s4, order) in enumerate([Reverse, Forward])
12+
if iseven(s1 + s2 + s3 + s4)
13+
target = [1, 2, 3]
14+
else
15+
target = [3, 2, 1]
16+
end
17+
@test target == sort([2, 3, 1], rev=rev, lt=lt, by=by, order=order)
18+
end
19+
end
20+
end
21+
end
22+
23+
@test [1 => 3, 2 => 5, 3 => 1] ==
24+
sort([1 => 3, 2 => 5, 3 => 1]) ==
25+
sort([1 => 3, 2 => 5, 3 => 1], by=first) ==
26+
sort([1 => 3, 2 => 5, 3 => 1], rev=true, order=Reverse) ==
27+
sort([1 => 3, 2 => 5, 3 => 1], lt= >, order=Reverse)
28+
29+
@test [3 => 1, 1 => 3, 2 => 5] ==
30+
sort([1 => 3, 2 => 5, 3 => 1], by=last) ==
31+
sort([1 => 3, 2 => 5, 3 => 1], by=last, rev=true, order=Reverse) ==
32+
sort([1 => 3, 2 => 5, 3 => 1], by=last, lt= >, order=Reverse)
33+
34+
35+
struct SomeOtherOrder <: Base.Order.Ordering end
36+
37+
@test_throws ErrorException sort([1, 2, 3], lt=(a, b) -> a - b < 0, order=SomeOtherOrder())
38+

0 commit comments

Comments
 (0)