RustArena

Step 5 of 7

Ordering: PartialOrd and Ord for sorting

v.sort() needs the items to implement Ord, a total order: for any two values, one is less, greater or equal. Deriving Ord (previous module) compares the fields in the order they're written. When you need a different order, implement it yourself.

A leaderboard: higher scores first, and players with the same score alphabetically.

use std::cmp::Ordering;

#[derive(Debug, PartialEq, Eq)]
struct Player {
    name: String,
    score: u32,
}

impl Ord for Player {
    fn cmp(&self, other: &Self) -> Ordering {
        other.score.cmp(&self.score).then(self.name.cmp(&other.name))
    }
}

impl PartialOrd for Player {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

fn main() {
    let mut board = vec![
        Player { name: String::from("cy"), score: 80 },
        Player { name: String::from("bo"), score: 95 },
        Player { name: String::from("al"), score: 80 },
    ];
    board.sort();
    for p in &board {
        println!("{} {}", p.score, p.name);
    }
}
95 bo
80 al
80 cy

Piece by piece:

Keep them consistent

==, cmp and < must agree: if cmp says Equal, == must say true. Here PartialEq compares both fields and cmp uses both fields, so they agree. If cmp ignored the name, two players with the same score would be "equal" for sorting but not for ==, and collections like BTreeSet would treat them as duplicates. Compare the same fields in all of them.

Implement Ord, or use sort_by?

Implement Ord when the type has one natural order that everyone expects: versions, priorities, dates. For a one-off order in one place ("sort these players by name for this report"), use sort_by or sort_by_key from the vectors module and leave the type alone:

board.sort_by(|a, b| a.name.cmp(&b.name));

Floats aren't Ord

f64 has NaN, which isn't less, equal or greater than anything, so floats only implement PartialOrd:

fn main() {
    let mut times: Vec<f64> = vec![9.58, 9.63, 9.69];
    times.sort();
    println!("{:?}", times);
}
error[E0277]: the trait bound `f64: Ord` is not satisfied

Use times.sort_by(|a, b| a.total_cmp(b));. total_cmp gives every float (even NaN) a fixed place in the order.

Common mistakes

Your task

Implement Ord and PartialOrd for Task so that sorting puts the highest priority first, then the earliest due day, then the title alphabetically.

Challenge 2

Sort Employees by years of service, most first, and for equal years by name alphabetically. Implement Ord and PartialOrd.

Challenge 3

Write fn leaderboard(entries: &[(&str, f64)]) -> Vec<String> for a race: each entry is a name and a time in seconds. Return lines like 1. ana 9.58, fastest time first, with equal times ordered by name. Use sort_by on a copy of the entries (entries.to_vec()), with total_cmp for the times and .then(...) for the names. Print times with {}, so 12.0 shows as 12.

Previous: Operator overloading Next: Default and AsRef