Skip to content

sort ignores ordering when by is defined #26741

Closed
@NymanLauri

Description

@NymanLauri

The sort method seems to ignore the ordering when by is defined:

Main> sort!([5,2,7], by = abs, order = Base.Order.Reverse)
3-element Array{Int64,1}:
 2
 5
 7

Main> sort!([5,2,7], by = abs, order = Base.Order.Forward)
3-element Array{Int64,1}:
 2
 5
 7

Version:

Main> versioninfo()
Julia Version 0.7.0-DEV.4690
Commit 78c7d87369* (2018-03-23 22:25 UTC)
Platform Info:
  OS: Windows (x86_64-w64-mingw32)
  CPU: Intel(R) Core(TM) i5-4440 CPU @ 3.10GHz
  WORD_SIZE: 64
  LIBM: libopenlibm
  LLVM: libLLVM-3.9.1 (ORCJIT, haswell)
Environment:

The reason for this lies in ordering.jl:

abstract type Ordering end

struct ForwardOrdering <: Ordering end
struct ReverseOrdering{Fwd<:Ordering} <: Ordering
    fwd::Fwd
end

ReverseOrdering(rev::ReverseOrdering) = rev.fwd
ReverseOrdering(fwd::Fwd) where {Fwd} = ReverseOrdering{Fwd}(fwd)

const DirectOrdering = Union{ForwardOrdering,ReverseOrdering{ForwardOrdering}}

struct By{T} <: Ordering
    by::T
end

struct Lt{T} <: Ordering
    lt::T
end

lt(o::ForwardOrdering,       a, b) = isless(a,b)
lt(o::ReverseOrdering,       a, b) = lt(o.fwd,b,a)
lt(o::By,                    a, b) = isless(o.by(a),o.by(b))
lt(o::Lt,                    a, b) = o.lt(a,b)

_ord(lt::typeof(isless), by::typeof(identity), order::Ordering) = order
_ord(lt::typeof(isless), by,                   order::Ordering) = By(by)
_ord(lt,                 by::typeof(identity), order::Ordering) = Lt(lt)
_ord(lt,                 by,                   order::Ordering) = Lt((x,y)->lt(by(x),by(y)))

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

function ord(lt, by, rev::Bool, order::Ordering=Forward)
    o = _ord(lt, by, order)
    return rev ? ReverseOrdering(o) : o
end

Essentially the problem is that the specified ordering is not taken into account when lt and by are not default:

_ord(lt::typeof(isless), by::typeof(identity), order::Ordering) = order
_ord(lt::typeof(isless), by,                   order::Ordering) = By(by)
_ord(lt,                 by::typeof(identity), order::Ordering) = Lt(lt)
_ord(lt,                 by,                   order::Ordering) = Lt((x,y)->lt(by(x),by(y)))

In sort.jl, this ordering is then passed down to lt to determine how two elements compare to each other.

If we want to support the ordering option in addition to rev, I think ordering.jl could be implemented in the following manner:

abstract type MyOrdering end

# Some base ordering
struct BinaryOp{T} <: MyOrdering
    binary_op::T
end

# Higher order ordering: wraps args in `by(...)`
struct By{O<:MyOrdering,T} <: MyOrdering
    by::T
    binary_op::O
end

# Higher order ordering: reverses argument of binary operator
struct ReverseOrdering{O<:MyOrdering} <: MyOrdering
    binary_op::O
end

# Higher order ordering. A no-op...
struct ForwardOrdering{O<:MyOrdering} <: MyOrdering
    binary_op::O
end

(o::BinaryOp)(a, b) = o.binary_op(a, b)
(o::By)(a, b)       = o.binary_op(o.by(a), o.by(b))
(o::ReverseOrdering)(a, b)  = o.binary_op(b, a)
(o::ForwardOrdering)(a, b)  = o.binary_op(a, b)

const DefaultOp = BinaryOp(<)
const MyForward = ForwardOrdering(By(identity,BinaryOp(<)))
const MyReverse = ReverseOrdering(By(identity,BinaryOp(<)))

_ord(lt, by, order::typeof(MyForward)) = ForwardOrdering(By(by,BinaryOp(lt)))
_ord(lt, by, order::typeof(MyReverse)) = ReverseOrdering(By(by,BinaryOp(lt)))

ord(lt, by, rev::Nothing, order::MyOrdering=MyForward) = _ord(lt, by, order)

function ord(lt, by, rev::Bool, order::MyOrdering=MyForward)
    o = _ord(lt, by, order)
    return rev ? ReverseOrdering(o) : o
end

The functions in sort.jl would need to be modified so that comparison of two elements does not happen with lt but through the MyOrdering instance, for example the function call lt(o, v[i], pivot) would be replaced by o(v[i], pivot) . Some examples:

function example()
    @show BinaryOp(<)(-51, 10)
    @show By(abs, BinaryOp(<))(-51, 10)
    @show Reverse(By(abs, BinaryOp(<)))(-51, 10)
    @show Reverse(Reverse(By(abs, BinaryOp(<))))(-51, 10)
end

function example_2()
    @show ord(<,identity,false)(1,2)
    @show ord(<,abs,false)(1,2)
    @show ord(<,abs,true)(1,2)
    @show ord(<,abs,true)(1,-2)
    @show ord(<,abs,true,MyForward)(1,-2)
    @show ord(<,abs,true,MyReverse)(1,-2)
end
Main> example()
(BinaryOp(<))(-51, 10) = true
(By(abs, BinaryOp(<)))(-51, 10) = false
(Reverse(By(abs, BinaryOp(<))))(-51, 10) = true
(Reverse(Reverse(By(abs, BinaryOp(<)))))(-51, 10) = false
false

Main> example_2()
(ord(<, identity, false))(1, 2) = true
(ord(<, abs, false))(1, 2) = true
(ord(<, abs, true))(1, 2) = false
(ord(<, abs, true))(1, -2) = false
(ord(<, abs, true, MyForward))(1, -2) = false
(ord(<, abs, true, MyReverse))(1, -2) = true
true

Any ideas? Comments? Incidentally, the current implementation is not type stable and neither is this one.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions