Skip to content

← Collections, Text and First Iterators step 21 of 21

Easy Primitives

Everything you can collect into

Turn a list of (key, value) pairs into an index.

pub fn index_and_dedup(pairs: Vec<(String, i64)>) -> (Vec<String>, BTreeMap<String, i64>)

Return the distinct keys, sorted ascending, and a map from key to value. When a key appears more than once, the map holds the last value seen — [("a", 1), ("a", 9)] gives {"a": 9}, not {"a": 1}.

collect is not “make a Vec”

collect builds anything that implements FromIterator. That is a much bigger set than most people use, and knowing it turns three-line loops into one-liners:

iter.collect::<Vec<T>>()
iter.collect::<VecDeque<T>>()
iter.collect::<String>()             // from char or &str items
iter.collect::<HashSet<T>>()         // dedup, in one call
iter.collect::<BTreeSet<T>>()        // dedup AND sort, in one call
iter.collect::<HashMap<K, V>>()      // from (K, V) pairs
iter.collect::<BTreeMap<K, V>>()     // ...ordered
iter.collect::<BinaryHeap<T>>()

and two that surprise everyone the first time:

// Iterator<Item = Result<T, E>>  ->  Result<Vec<T>, E>
let parsed: Result<Vec<i32>, _> = strings.iter().map(|s| s.parse()).collect();

// Iterator<Item = Option<T>>  ->  Option<Vec<T>>
let all: Option<Vec<T>> = maybe_items.collect();

The Result one short-circuits: the first Err stops the iteration and becomes the whole answer, otherwise you get every Ok value in a Vec. That single line replaces a loop with an early return, and you will use it constantly once the errors track begins.

Collecting into a map: the last-wins rule

let map: BTreeMap<String, i64> = pairs.into_iter().collect();

FromIterator for both map types is defined in terms of insert, and insert overwrites. So duplicate keys are silent data loss, and the survivor is the last one. That is the opposite of entry().or_insert(v), which keeps the first.

Neither is wrong; they are different operations, and this problem specifies the collect one. What is dangerous is not knowing which you wrote — the test case with a repeated key exists so that you find out here rather than in production.

::: question HashSet::insert returns whether the value was new, and HashMap::insert returns the displaced value. Why does collect throw both away? Because FromIterator has one job — turn a stream of items into a collection — and it has to work identically for every collection. There is no return channel in collect for “and by the way, three of these were duplicates”.

If you need to know, do not use collect. Write the loop and look at what insert gives you back:

for (k, v) in pairs {
    if let Some(old) = map.insert(k.clone(), v) {
        eprintln!("duplicate key {k}: {old} replaced by {v}");
    }
}

This is a small, general lesson about combinators: they are compact because they discard the information you usually do not want. When you do want it, drop back to the loop rather than trying to smuggle it out. :::

Order, and the trap the harness will catch

Collecting into a HashSet is the shortest correct dedup in the language. But HashSet iteration order is randomised per run — the same randomisation you met earlier in this track — so:

let keys: Vec<String> = pairs.iter().map(|(k, _)| k.clone())
    .collect::<HashSet<_>>()
    .into_iter()
    .collect();          // deduped, and in a different order every run

is a test that passes on your machine and fails on the grader. Two fixes:

  • route through a BTreeSet instead — dedup and sort in one step;
  • or sort the Vec explicitly afterwards.

For this problem there is a third and shorter route: you are already building a BTreeMap, whose keys() are unique and in order by construction. map.keys().cloned().collect() is the whole thing.

The two errors you will meet

E0283, “type annotations needed.” collect is generic over its output, and when nothing in the surrounding code pins it down the compiler refuses to guess:

error[E0283]: type annotations needed
  = note: cannot satisfy `_: FromIterator<i32>`
help: consider giving `c` an explicit type

Fix it with a binding annotation (let v: Vec<_> = ...) or the turbofish (.collect::<Vec<_>>()). Prefer the annotation when the type is the interesting part of the line, and the turbofish when it is incidental.

E0277, which is what the starter gives you: the iterator’s item type and the destination disagree.

error[E0277]: a value of type `Vec<String>` cannot be built from an
              iterator over elements of type `&String`

pairs.iter().map(|(k, _)| k) yields &String, and a Vec<String> needs owned values. .cloned() is the fix and the allocation is the honest price of the owned return type. This is the same message you have now seen from set operations and from BTreeMap::range — once you recognise it, it is a two-second diagnosis for the rest of your Rust life.

One more thing the starter gets wrong

Even after the compile error is fixed, the starter’s map is built with entry(..).or_insert(v), which keeps the first value. The specification says last. Fixing that is the difference between code that compiles and code that is correct, and only the test cases can tell you.

Remember the grade is compile + tests + clippy -D warnings.

Loading visualization…