Consecutive findmin and findmax

I want to find the index of an element after consecutive min and max operations.

I have a working code, but it’s ugly and probably inefficient since it makes unneeded iterations.
Also it’s not flexible, in the sense it’s not super easy to change the min/max operation sequence.

Below a MWE and my code, where I basically want the element with minimum a. Out of these the one with maximum b. And out of these the one with minimum c

struct My
    a::Int
    b::Int
    c::Int
end
geta(my::My) = my.a
getb(my::My) = my.b
getc(my::My) = my.c

import Random
rng = Random.MersenneTwister(0)
mys = [My(x,y,z) for x in rand(rng, 1:5, 5) for y in rand(rng, 1:5, 5) for z in rand(rng, 1:5, 5)];

amin, _ = findmin(geta, mys);

aminfilter = filter(x -> geta(x) == amin, mys);
bmax, _ = findmax(getb ,aminfilter);

aminbmaxfilter = filter(x -> geta(x) == amin && getb(x) == bmax ,mys);
cmin, _ = findmin(getc ,aminbmaxfilter);

result = findfirst(x -> geta(x) == amin && getb(x) == bmax && getc(x) == cmin, mys)

yielding

24
julia> mys[24]
1-element Vector{My}:
 My(3, 5, 1)

I would love to have a simple nice expression like this:

# psedocode
findmin(getc, findmax(getb, findmin(geta, mys)))

How would you solve the above problem in a more elegant way?

Dunno if this qualifies as elegant, but …

using SplitApplyCombine
function selectall(itr, f,r)
       d = groupview(f, itr)
       d[r(keys(d))]
end
selectall(itr, fr) = selectall(itr, fr...) # binary version
reduce(selectall, ((geta,minimum), (getb, maximum), (getc, minimum)), init=mys)
1 Like
reduce(enumerate(mys)) do (i,x), (j,y)
    x.a > y.a && return j=>y
    x.a == y.a && begin
        x.b < y.b && return j=>y
        x.b == y.b &&
            x.c > y.c && return j=>y
    end
    i=>x
end
1 Like

Another method:

julia> Base.isless(x::My, y::My) = isless((x.a,-x.b,x.c),(y.a,-y.b,y.c))

julia> findmin(mys)
(My(3, 5, 1), 24)
6 Likes

Check also this option:

partialsortperm(mys, 1, by = x -> (x.a, -x.b, x.c))      
24
3 Likes

this function does exactly what I want! Thanks!

The answer from @Dan is also very elegant but it appears not to be flexible… I wish there was a by argument in the findmin function. Overloading Base.isless everytime I need a different functionality doesn’t work nicely.

Thanks everybody for the nice solutions.

findmin(x -> (x.a, -x.b, x.c), mys)

4 Likes