Reverse specific key when sorting with multiple keys
Here's a similar approach to the problem: create a function for chaining multiple orderings:
/// chain two orderings: the first one gets more priority
fn chain_ordering(o1: Ordering, o2: Ordering) -> Ordering {
match o1 {
Ordering::Equal => o2,
_ => o1,
}
}
Then use sort_by
, possibly with pattern matching, to produce the ordering of each key:
#[derive(Debug, PartialEq)]
struct HeroSkill(&'static str, &'static str);
fn main() {
// a vector of hero names and super powers
let mut v = vec![
HeroSkill("Bob", "X"),
HeroSkill("Bob", "Y"),
HeroSkill("Alice", "X")
];
// sort by name, then by super power, where Y is more powerful than X
v.sort_by(|&HeroSkill(name1, power1), &HeroSkill(name2, power2)| {
chain_ordering(name1.cmp(name2), power1.cmp(power2).reverse())
});
assert_eq!(v, vec![
HeroSkill("Alice", "X"),
HeroSkill("Bob", "Y"),
HeroSkill("Bob", "X")
]);
}
Playground
Since Rust 1.19, the std::cmp::Reverse
struct wraps a value and implements PartialOrd
and Ord
by calling partial_cmp
and cmp
with swapped arguments in order to return the reversed order. Just wrap the key to sort in descending order:
vec.sort_by_key(|k| (Reverse(foo(k)), bar(k)));
Before Rust 1.19, you can use the revord
crate (documentation) which provides the struct RevOrd
which provides the same benefit::
vec.sort_by_key(|k| (RevOrd(foo(k)), bar(k)));
You can use sort_by
paired with Ordering::reverse
instead of sort_by_key
.
use std::cmp::Ordering;
#[derive(Debug)]
struct Foo(&'static str, u8);
impl Foo {
fn name(&self) -> &str { self.0 }
fn len(&self) -> u8 { self.1 }
}
fn main() {
let mut vec = vec![Foo("alpha", 1), Foo("beta", 2), Foo("beta", 1)];
vec.sort_by(|a, b| {
match a.name().cmp(b.name()).reverse() {
Ordering::Equal => a.len().cmp(&b.len()),
other => other,
}
});
println!("{:?}", vec);
}
This sorts in reverse alphabetical order, then ties are sorted in ascending numerical order:
[Foo("beta", 1), Foo("beta", 2), Foo("alpha", 1)]
Since Rust 1.17 (via RFC 1677), you can write it like this:
vec.sort_by(|a, b| {
a.name().cmp(b.name()).reverse()
.then(a.len().cmp(&b.len()))
});
If you have something that can naturally be negated / inverted, you can simply negate the key.