We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Closures and Iterators step 12 of 28
zip and enumerate: pair two lists and report the leftovers
Pair up names with scores and report what did not pair.
pub fn interleave_report(names: Vec<String>, scores: Vec<i64>) -> Vec<String>
Emit one line per pair, formatted "idx:name=score" with idx
1-based, stopping when either list runs out. Then emit exactly one final
line, "unmatched:K", where K is how many entries the longer list had left
over.
names = ["a", "b", "c"]
scores = [10]
-> ["1:a=10", "unmatched:2"]
The "unmatched:K" line is always present, even when K is 0 and even
when both lists are empty.
zip truncates, and never tells you
fn zip<U: IntoIterator>(self, other: U) -> Zip<Self, U::IntoIter>
Zip::next calls next on both sides and yields Some((a, b)) only if
both produced a value. The first None on either side ends the whole
iterator. So zip silently stops at the shorter input — no error, no
padding, no warning.
That is usually what you want, and it is the number-one source of “why did my output come out short”. If the mismatch matters, you have to detect it yourself, before or beside the zip — which is exactly what this problem asks for. The iterator cannot tell you afterwards; it has already forgotten.
Compute the leftover count from the lengths, and use usize::abs_diff:
let leftover = names.len().abs_diff(scores.len());
Not names.len() - scores.len(). len() is usize, which is unsigned, so
when scores is longer that subtraction underflows. This binary is built
with -O, so it does not panic — it wraps. The starter does exactly this and
reports:
unmatched:18446744073709551614
That is usize::MAX - 1. A debug build would have panicked with “attempt to
subtract with overflow”; a release build ships the nonsense. Whenever you
subtract two lengths, ask which one is bigger — and if you do not know,
abs_diff does.
enumerate is the replacement for the index loop
fn enumerate(self) -> Enumerate<Self> // Item = (usize, Self::Item)
The counter is always 0-based and always counts what reaches it, not
positions in the original source. That second part matters: put enumerate
after a filter and you number the survivors; put it before and you number
the originals. Both are useful, and picking the wrong one is a silent bug.
Here the index must be 1-based, so i + 1 at the format site. Do not try to
start the counter at one; there is no such option, and reaching for
.skip(1) or a manual counter to fake it is how the starter got into
trouble.
Nesting matters too:
a.iter().enumerate().zip(b) // Item = ((usize, &A), B)
a.iter().zip(b).enumerate() // Item = (usize, (&A, B))
Same elements, different tuple shape. The second is what you want here, and
it destructures as |(i, (name, score))|.
Two default-on lints are doing the teaching
The starter uses a range loop with a hand-rolled counter, and clippy says:
error: the variable `idx` is used as a loop counter
help: consider using: `for (idx, ...) in ... .enumerate()`
[clippy::explicit_counter_loop]
Its sibling needless_range_loop fires on for i in 0..v.len() when i
is only used to index one slice, and tells you to iterate the slice directly.
Between them, clippy essentially bans the C-style index loop in idiomatic
Rust — which is fine, because zip and enumerate cover the cases it was
used for, and they do it without a bounds check.
The rest of the family, briefly
-
chain(other)— run one iterator then the next. Both must have the sameItem. -
rev()— requiresDoubleEndedIterator. Slices and ranges have it;HashMapiteration does not. -
cycle()— repeat forever. RequiresClone, and is infinite: pair it withziportakeor you have written a hang. Clippy’sinfinite_iteris deny-by-default and catches the obvious cases.
zip with cycle is the idiomatic “pad the shorter side”, if you ever need
the opposite of truncation.
Grade is compile + tests + clippy -D warnings.
Loading visualization…
Stuck?
Rust reference solution
Sign in to attempt this problem and reveal the reference solution.