Minmax with `lt` keyword argument?

Is there a “minmax” that can have a lt keyword like what sort does? For example, some method that does the following:

minmax(x, y; lt=isless) = lt(x,y) ? (x,y) : (y,x)

Can you give a small example in running code, or if not possible, an example with small input and the expected output.

I’ve updated the op

min, max = sort([x, y], lt=...) :grimacing:

I don’t think there is function like that. It seems strange to ask for it though. For example, if you wish to switch the order (lt=(x, y) -> x ≥ y), why not simply do max, min = minmax(x, y)?

1 Like
ifelse(lt(x,y), (x,y), (y,x))

is very simple and fast.

I would not call this minmax, it has different semantics.

But defining and using any function in Julia like this is totally fine.
But note, the returning type is a Tuple which type depends on the arguments. So this function is not type stable since the return type would depend on the value of the numbers.

julia> function my_function(x, y; lt=isless)
           return lt(x,y) ? (x,y) : (y,x)
       end
my_function (generic function with 2 methods)

julia> @code_warntype my_function(1, 1.0)
MethodInstance for my_function(::Int64, ::Float64)
  from my_function(x, y; lt) @ Main REPL[12]:1
Arguments
  #self#::Core.Const(my_function)
  x::Int64
  y::Float64
Body::Union{Tuple{Float64, Int64}, Tuple{Int64, Float64}}
1 ─ %1 = Main.:(var"#my_function#3")(Main.isless, #self#, x, y)::Union{Tuple{Float64, Int64}, Tuple{Int64, Float64}}
└──      return %1

More safe would be to restrict the function arguments to have the same type, or do type promotion to the same type.

julia> function my_function2(x::T, y::T; lt=isless) where T
           return lt(x,y) ? (x,y) : (y,x)
       end
my_function2 (generic function with 1 method)

julia> my_function2(1, 1.0)
ERROR: MethodError: no method matching my_function2(::Int64, ::Float64)

Closest candidates are:
  my_function2(::T, ::T; lt) where T
   @ Main REPL[16]:1

Stacktrace:
 [1] top-level scope
   @ REPL[17]:1

julia> my_function2(1, 1)
(1, 1)

julia> @code_warntype my_function2(1, 1)
MethodInstance for my_function2(::Int64, ::Int64)
  from my_function2(x::T, y::T; lt) where T @ Main REPL[16]:1
Static Parameters
  T = Int64
Arguments
  #self#::Core.Const(my_function2)
  x::Int64
  y::Int64
Body::Tuple{Int64, Int64}
1 ─ %1 = Main.:(var"#my_function2#5")(Main.isless, #self#, x, y)::Tuple{Int64, Int64}
└──      return %1

1 Like

It seems like this is maybe looking for something a tad more generic like “sort a short tuple.”

I found this snippet I had lying around. The syntax here is a tad unusual due to outsourcing the ordering to my makeorder function, but something like the following might fit your bill (I leave it as an exercise to the reader to write the length=2 version). You can refactor the makeorder component to expose the sorting keywords directly within sorttup.

makeorder(;lt=isless,by=identity,rev::Union{Bool,Nothing}=nothing,order::Base.Order.Ordering=Base.Order.Forward,) = Base.Order.ord(lt,by,rev,order)

function sorttup((a,b,c)::NTuple{3},ordr=makeorder())
	# sort 3 values
	a,b = ifelse(Base.lt(ordr,b,a),(b,a),(a,b))
	b,c = ifelse(Base.lt(ordr,c,b),(c,b),(b,c))
	a,b = ifelse(Base.lt(ordr,b,a),(b,a),(a,b))
	return a,b,c
end

function sorttup((a,b,c,d)::NTuple{4},ordr=makeorder())
	# sort 4 values
	a,b = ifelse(Base.lt(ordr,b,a),(b,a),(a,b))
	c,d = ifelse(Base.lt(ordr,d,c),(d,c),(c,d))
	a,c = ifelse(Base.lt(ordr,c,a),(c,a),(a,c))
	b,d = ifelse(Base.lt(ordr,d,b),(d,b),(b,d))
	b,c = ifelse(Base.lt(ordr,c,b),(c,b),(b,c))
	return a,b,c,d
end
1 Like

There is also TupleTools.jl and this sort: Home · TupleTools.jl

2 Likes

TupleTools.jl is slower than SortingNetworks.jl but the latter doesn’t support lt or by. It’s exactly the motivation of this post though.

You apply this function to some big data?
Can you share an example for it?

Not too big, but kind of low-level. I need to sort intervals before calculating the union of them.

Other answers are good; I just want to point out that you may also be interested in

extrema(f, (x,y))

which always uses < for comparisons, but allows you to preprocess the input with the function f. (Plus it extends nicely to longer collections.) In the trivial case, it compiles down to exactly the same code as other solutions.

Also note that most answers use the tuple (x, y) instead of the vector [x, y] because the vector would allocate, whereas the tuple will not.

3 Likes