2

It's quite common to compare data with precedence, for a struct which has multiple members which can be compared, or for a sort_by callback.

// Example of sorting a: Vec<[f64; 2]>, sort first by y, then x,
xy_coords.sort_by(
    |co_a, co_b| {
        let ord = co_a[1].cmp(&co_b[1]);
        if ord != std::cmp::Ordering::Equal {
            ord
        } else {
            co_a[0].cmp(&co_b[0])
        }
    }
);

Is there a more straightforward way to perform multiple cmp functions, where only the first non-equal result is returned?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
ideasman42
  • 42,413
  • 44
  • 197
  • 320

1 Answers1

6

perform multiple cmp functions, where only the first non-equal result is returned

That's basically how Ord is defined for tuples. Create a function that converts your type into a tuple and compare those:

fn main() {
    let mut xy_coords = vec![[1, 0], [-1, -1], [0, 1]];

    fn sort_key(coord: &[i32; 2]) -> (i32, i32) {
        (coord[1], coord[0])
    }

    xy_coords.sort_by(|a, b| {
        sort_key(a).cmp(&sort_key(b))
    });
}

Since that's common, there's a method just for it:

xy_coords.sort_by_key(sort_key);

It won't help your case, because floating point doesn't implement Ord.

One of many possibilities is to kill the program on NaN:

xy_coords.sort_by(|a, b| {
    sort_key(a).partial_cmp(&sort_key(b)).expect("Don't know how to handle NaN")
});

See also


There are times when you may not want to create a large tuple to compare values which will be ignored because higher priority values will early-exit the comparison.

Stealing a page from Guava's ComparisonChain, we can make a small builder that allows us to use closures to avoid extra work:

use std::cmp::Ordering;

struct OrdBuilder<T> {
    a: T,
    b: T,
    ordering: Ordering,
}

impl<T> OrdBuilder<T> {
    fn new(a: T, b: T) -> OrdBuilder<T> {
        OrdBuilder {
            a: a,
            b: b,
            ordering: Ordering::Equal,
        }
    }

    fn compare_with<F, V>(mut self, mut f: F) -> OrdBuilder<T>
        where F: for <'a> FnMut(&'a T) -> V,
              V: Ord,
    {
        if self.ordering == Ordering::Equal {
            self.ordering = f(&self.a).cmp(&f(&self.b));
        }
        self
    }

    fn finish(self) -> Ordering {
        self.ordering
    }
}

This can be used like

struct Thing {
    a: u8,
}

impl Thing {
    fn b(&self) -> u8 {
        println!("I'm slow!");
        42
    }
}

fn main() {
    let a = Thing { a: 0 };
    let b = Thing { a: 1 };

    let res = OrdBuilder::new(&a, &b)
        .compare_with(|x| x.a)
        .compare_with(|x| x.b())
        .finish();

    println!("{:?}", res);
}
Community
  • 1
  • 1
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • This answer is good for the example in the question, but doesn't address the question directly. There are times when you may not want to create a large tuple to compare values which will be ignored because higher priority values will early-exit the comparison. Basically - there are times its reasonable to avoid creating a tuple for comparison, where the approach in the question still applies. – ideasman42 Aug 24 '16 at 02:01
  • @ideasman42 *doesn't address the question directly* — that might be true *if* the question had **any** kind of comments about performance or avoiding creating things. The **question** was quite clear: *What's the most straightforward way to chain comparisons, yielding the first non-equal?* and it was answered pretty directly. You can't retroactively change the spirit of the question just because you didn't properly state what was important to you the first time around. – Shepmaster Aug 24 '16 at 02:21
  • I should have made this more clear, and I appreciate your answer. The question does ask for a *"way to perform multiple cmp functions"*, I was interested in this aspect specifically - a way to keep the comparison logic under the control of the closure (in this example). Otherwise I would have asked a more general question about comparisons. – ideasman42 Aug 24 '16 at 02:33