Mini-RFC: min_max

I’m in a need to find the minimum and the maximum in a slice. I could solve this by using the min and max functions after each other, but this will iterate over the slice twice, which is not optimal.

Instead, I think the solution would be to get both in only one iteration. The signature would be similar to this:

trait Iterator {
    fn min_max(self) -> (Option<Self::Item>, Option<Self::Item>);
    fn min_max_by<F>(self, compare: F) -> (Option<Self::Item>, Option<Self::Item>)
        where Self: Sized, F: FnMut(&Self::Item, &Self::Item) -> Ordering;
    fn min_max_by_key<B: Ord, F>(self, f: F) -> (Option<Self::Item>, Option<Self::Item>)
        where Self: Sized, F: FnMut(&Self::Item) -> B;
}

What do you think about this? Is this practical? Am I overseeing something? Is this just a special case not not generaly appliable?

As far as I understand, this doesn’t need an official RFC, but can be done via a PullRequest. Am I correct?

1 Like

There’s https://docs.rs/itertools/0.7.8/itertools/trait.Itertools.html#method.minmax

An interesting quirk of the API is that you’ll need to specifically handle the case where iterator contains a single element only.

3 Likes

Do you? Why don’t return that element twice? It is the min and the max at the same time. I don’t see a contradiction at all.

A solution with a fold looks a bit hairy:

fn main() {
    let data = [10, 80, 30, 50];

    let (min, max) =
        data
        .iter()
        .cloned()
        .fold(None, |m: Option<(u32, u32)>, x|
                     m.map_or(Some((x, x)), |(m1, m2)| Some((m1.min(x), m2.max(x)))))
        .unwrap();
    println!("{} {}", min, max);
}

You’ll need a T: Clone for that.

1 Like

True. Okay, I see the special need for the one element case.

Right, I don’t like hairy solutions, I like rusty solutions :wink:

So, there is a little chance to get this implemented, because Itertools already implements it (and because of that special case, one could avoid with an enum)?

1 Like

did you look at the itertools link? that’s exactly what it does. :wink:

That’s why i wrote that :wink:

My intuition is that the “it needs a new special-purpose enum” part might actually be the roughest bit – it opens a huge hole for bikeshedding, which isn’t necessarily fatal, but does mean it’s harder to be confident that the API is the right one to commit to for perpetuity.