We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Closures and Iterators step 19 of 28
min and max: the tie-breaking rule nobody reads
Find the lowest-scoring and highest-scoring entry.
pub fn extremes(entries: Vec<(String, i64)>) -> (Option<String>, Option<String>)
Return the name of the entry with the smallest score and the name of the
entry with the largest score. Empty input gives (None, None).
Ties are the entire problem. Resolve them exactly the way std does —
which is not symmetric, and which you almost certainly cannot guess.
The rule
From the standard library documentation, verbatim:
Iterator::max— If several elements are equally maximum, the last element is returned.
Iterator::min— If several elements are equally minimum, the first element is returned.
Read it twice. max keeps the last. min keeps the first. The same
asymmetry applies to every variant — max_by, max_by_key, min_by,
min_by_key.
Verified on rustc 1.95:
let v = [("a", 2), ("b", 5), ("c", 2), ("d", 5)];
v.iter().min_by_key(|(_, s)| *s) // Some(("a", 2)) <- first 2
v.iter().max_by_key(|(_, s)| *s) // Some(("d", 5)) <- last 5
Why it is asymmetric
It is not an accident and it is not a bug. Both are folds that compare the running best against each new element, and they use complementary comparisons:
-
minreplaces only when the new element is strictly less than the current best. An equal element loses, so the first one survives. -
maxreplaces when the new element is greater than or equal to the current best. An equal element wins, so the last one survives.
Those two choices are complementary on purpose. Take any two-element
sequence [a, b] where a and b compare equal: min returns a and
max returns b. They pick different elements rather than both picking
the same one — which is what you want if you are using the pair to split a
sequence, and what a stable sort would do. The point is not that you must
agree with the choice; it is that std specifies it, so there is a right
answer, and your reimplementation can be measured against it.
Why this problem exists
A hand-rolled loop gets this wrong with no compile error, no clippy
warning, and no failure on any input where the scores happen to be
distinct. The starter is exactly such a loop: it is correct on min and
wrong on max, and it fails only on the tie cases.
This is the kind of specified-but-unread behaviour that turns into a bug
report eighteen months later, when someone’s leaderboard starts naming a
different winner than the one on the dashboard. When two implementations of
“the maximum” disagree, they disagree about ties, and one of them is not
std.
The lesson generalises: do not reimplement a standard-library consumer.
min_by_key and max_by_key are one line each, are already correct, and
already document their behaviour.
The _by_key variants
fn max_by_key<B: Ord, F: FnMut(&Self::Item) -> B>(self, f: F) -> Option<Self::Item>
f extracts a comparison key; the element comes back, not the key. Two
things worth knowing:
-
The key closure is called on every element —
max_by_keyis not smart enough to cache it, so if computing the key is expensive, hoist it (.map(|x| (key(x), x)).max()) rather than paying twice per comparison. -
If you want to compare with a custom ordering rather than a key, that is
max_by, which takes a fullFnMut(&Item, &Item) -> Ordering.
Clippy will also push you off sort when you only wanted an extreme:
sorting to take the first element is O(n log n) for an O(n) question, and
unnecessary_sort_by catches some spellings of it.
f64 has no min/max here
Worth knowing before it surprises you: Iterator::min and Iterator::max
require Item: Ord, and f64 is not Ord — because NaN is not
comparable to anything, including itself. So [1.0f64, 2.0].iter().max()
does not compile. Use fold(f64::NEG_INFINITY, f64::max) if you are happy
with NaN being ignored, or max_by(|a, b| a.partial_cmp(b).unwrap()) if
you would rather it panicked, or total_cmp if you want the IEEE total
order. There is no free lunch; you have to say what NaN means to you.
Grade is compile + tests + clippy -D warnings.
Loading visualization…
Stuck?
Rust reference solution
Sign in to attempt this problem and reveal the reference solution.