Start learning free

LESSON 270 OF 679 · TUPLES, SORTING & BUILT-INS

Custom Sorting in Rust: sort_by, cmp and then

Lesson Read the full explanation

TL ; DR:

To sort by your own rule, give sort_by a closure. It compares two items with cmp.


let mut nums = vec![25, 13, 5, 41, 33];

nums.sort_by(|a, b| (a % 10).cmp(&(b % 10)).then(a.cmp(b)));
// [41, 13, 33, 5, 25]

let mut scores = vec![70, 95, 82];

scores.sort_by(|a, b| b.cmp(a));
// [95, 82, 70]


Full lesson:

You met sort() in the Shopping Cart project. It sorts a Vec from small to large, or from A to Z. Sometimes you need another order. The shortest word may have to come first, or the highest score.

Then use sort_by with a closure, as you wrote in Vec & Iterators. The closure gets two items, a and b. It returns how they compare.

The cmp method compares two values. It gives an Ordering. That is Less, Equal or Greater. With Less, a goes first.

  • Low to high: a.cmp(b).
  • High to low: b.cmp(a). Swapping the two flips the order.
  • Shortest first: a.len().cmp(&b.len()).

For two rules, chain them with then. The second comparison only counts when the first one is Equal.

Above, the first rule compares the last digits, a % 10 and b % 10. Numbers with the same last digit then go from small to large.

For one rule, sort_by_key is shorter. Its closure returns what to sort by, as in words.sort_by_key(|w| w.len()).

The sort_by method sorts the Vec itself, so it must be mut. To keep the original, sort a copy made with clone().


Your task

A pet shop sells toys. Their prices are in prices. Sort prices from high to low.

The shop's pets are in names. Sort names by their last letter, in alphabetical order. Names with the same last letter go from A to Z.

The code already prints both, one item per line.

Expected output:

30
12
8
5
Bella
Luna
Bo
Coco
Max
Rex
STEP 1 OF 2
    Ctrl + Enter to check

    RUST · TUPLES, SORTING & BUILT-INS

    Custom Sorting in Rust, explained

    When sort() is not enough

    The method sort() orders a Vec from small to large, or from A to Z. Sometimes you need another order: the shortest word first, or the highest score first. For those, use sort_by with a closure. The closure gets two items, a and b (as references), and says how they compare.

    Ordering and cmp

    The cmp method compares two values and gives an Ordering, which is Less, Equal or Greater. With Less, a goes first. That gives you the common orders:

    • low to high: a.cmp(b);
    • high to low: b.cmp(a), because swapping the two flips the order;
    • shortest first: a.len().cmp(&b.len()).
    let mut scores = vec![70, 95, 82];
    scores.sort_by(|a, b| b.cmp(a));
    println!("{:?}", scores);
    [95, 82, 70]

    Two rules with then

    For two rules, chain them with then. The second comparison only counts when the first one is Equal. Here the first rule compares the last digits, a % 10 against b % 10, and numbers with the same last digit go from small to large:

    let mut nums = vec![25, 13, 5, 41, 33];
    nums.sort_by(|a, b| (a % 10).cmp(&(b % 10)).then(a.cmp(b)));
    println!("{:?}", nums);
    [41, 13, 33, 5, 25]

    The last digits are 1, 3, 3, 5 and 5, so 41 comes first, then 13 and 33, then 5 and 25. Within each pair the second rule puts the smaller number first.

    sort_by_key for one rule

    When there is a single rule, sort_by_key is shorter. Its closure returns the value to sort by:

    let mut words = vec!["pear", "fig", "banana"];
    words.sort_by_key(|w| w.len());
    println!("{:?}", words);
    ["fig", "pear", "banana"]

    Equal items keep their order

    These sorts are stable: items that compare as equal stay in the order they had. With sort_by_key(|w| w.len()) on ["pear", "kiwi", "fig", "plum"] the result is ["fig", "pear", "kiwi", "plum"]. The three 4-letter words keep their original order of pear, kiwi, plum. That is why a second rule is only needed when you want a specific order among the ties, such as A to Z.

    Things to know

    • The vector must be mut. Both sort_by and sort_by_key reorder the vector itself and return nothing. To keep the original order, sort a copy made with clone().
    • Decimals have no cmp. A vector of f64 can't use sort(), because decimals can be NaN, which has no order; the compiler reports that Ord is not satisfied. Use a.partial_cmp(b).unwrap() in sort_by instead.
    • Swap, don't negate. Reversing the order is b.cmp(a), which works for strings too.

    The exercise, step by step

    A pet shop sells toys with prices in prices and has pets in names. The task sorts prices from high to low, and names by last letter, with names that share a last letter from A to Z.

    1. Prices: prices.sort_by(|a, b| b.cmp(a)). Comparing b with a flips the order.
    2. Names: the first rule compares the last letters. a[a.len() - 1..] is a slice from the last position to the end, which holds just the last letter (the names here use plain English letters). Chain .then(a.cmp(b)) so that Max comes before Rex, as both end in x.
    fn main_function() {
        let mut prices = vec![12, 5, 30, 8];
        let mut names = vec!["Rex", "Luna", "Bella", "Max", "Bo", "Coco"];
    
        prices.sort_by(|a, b| b.cmp(a));
        for price in &prices {
            println!("{}", price);
        }
    
        names.sort_by(|a, b| a[a.len() - 1..].cmp(&b[b.len() - 1..]).then(a.cmp(b)));
        for name in &names {
            println!("{}", name);
        }
    }

    It prints the prices 30, 12, 8, 5, then the names Bella, Luna, Bo, Coco, Max, Rex, one per line. Bella and Luna end in a, Bo and Coco in o, and Max and Rex in x.

    Key takeaways

    • sort_by takes a closure that returns an Ordering built with cmp.
    • b.cmp(a) sorts high to low.
    • .then(...) adds a second rule that only matters on ties.
    • sort_by_key is the short form for one rule.
    Keep going with Rust500+ step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free