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. Bothsort_byandsort_by_keyreorder the vector itself and return nothing. To keep the original order, sort a copy made withclone(). - Decimals have no
cmp. A vector off64can't usesort(), because decimals can be NaN, which has no order; the compiler reports thatOrdis not satisfied. Usea.partial_cmp(b).unwrap()insort_byinstead. - 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.
- Prices:
prices.sort_by(|a, b| b.cmp(a)). Comparingbwithaflips the order. - 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_bytakes a closure that returns anOrderingbuilt withcmp.b.cmp(a)sorts high to low..then(...)adds a second rule that only matters on ties.sort_by_keyis the short form for one rule.
