Skip to content

← Closures and Iterators step 19 of 28

Medium Primitives

Choose the first minimum and last maximum

Return the names of the lowest-scoring and highest-scoring entries:

pub fn extremes(entries: Vec<(String, i64)>) -> (Option<String>, Option<String>)

Compare scores only. When scores tie, choose the first minimum and the last maximum in input order. Names do not act as a secondary comparison key.

Entries Lowest name Highest name
[("a", 3), ("b", 1), ("c", 5)] Some("b") Some("c")
[("a", 2), ("b", 5), ("c", 2), ("d", 5)] Some("a") Some("d")
[("z", 7), ("a", 7)] Some("z") Some("a")
[("only", 7)] Some("only") Some("only")
[] None None

The return type contains owned String values. Preserve the selected names exactly, including empty names and Unicode characters. Scores may be negative and may use the full i64 range.

Use the iterator's tie rules

min_by_key keeps the first item whose key is minimal. max_by_key keeps the last item whose key is maximal. “First” and “last” refer to iteration order; this exercise uses the vector's original order.

Both methods return an item, not its key. Here the key is the score, while the item contains both the name and score:

let entries = [("a", 2), ("b", 5), ("c", 2), ("d", 5)];
let lowest = entries.iter().min_by_key(|(_, score)| *score);
let highest = entries.iter().max_by_key(|(_, score)| *score);
assert_eq!(lowest, Some(&("a", 2)));
assert_eq!(highest, Some(&("d", 5)));

iter() borrows the entries. After selecting an entry, use Option::map to obtain an owned copy of its name. Empty iterators return None, which stays None when mapped. Two fresh calls to entries.iter() let you compute both extremes without consuming the vector on the first pass.

The same tie policies apply to the corresponding min/max and min_by/max_by methods, using their value or comparator ordering.

What is wrong with the starter?

Its loop keeps the first minimum correctly. It also keeps the first maximum: on an equal score, the highest branch retains its existing entry. That is the mismatch to fix.

To express the required policy in a loop, replace the minimum only for a strictly smaller score, but replace the maximum for an equal or greater score. Using the standard iterator methods is another direct way to obtain those policies. Ties in non-extreme scores do not affect the answer, and identical names can hide a wrongly selected entry in the returned output.

Keep the comparison focused on the score

Calling .max() directly on (name, score) tuples compares the name first. Mapping entries to (score, entry) and calling .max() compares the whole entry when scores tie. Neither expresses this task's score-only ordering. Use a score key or a comparator that reads only the scores.

There is no need to precompute keys to avoid evaluating them twice per comparison: the current standard implementation of max_by_key and min_by_key evaluates each item's key once per pass and carries it with the candidate. Computing both extremes in two passes still evaluates each entry's score key in each pass. For this task, that key is just an i64 copy.

Each pass examines the entries once. Sorting is unnecessary, and changing the iteration order can also change which tied name is returned.