We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Closures and Iterators step 15 of 28
scan: a running balance that stops at the floor
Produce the running balances for a sequence of changes, stopping before a balance would fall below the allowed minimum.
pub fn balances(deltas: Vec<i64>, floor: i64) -> Vec<i64>
Start at zero and apply the deltas from left to right. After each addition,
append the new balance if it is at least floor. Otherwise, stop and return
the balances already collected. Do not include the rejected balance or
process later deltas, even if they would raise it again.
Also stop if an addition cannot be represented as an i64. Use checked
addition so this behaves consistently across build settings. Overflow does
not append a wrapped or saturated value.
| Deltas | Floor | Output |
|---|---|---|
[10, -3, 5] |
0 |
[10, 7, 12] |
[10, -20, 5] |
0 |
[10] |
[-1] |
0 |
[] |
[0, 0] |
0 |
[0, 0] |
[5, -1] |
5 |
[5] |
[i64::MAX, 1] |
0 |
[i64::MAX] |
An empty input returns an empty vector. The initial zero is not emitted or checked against the floor; only balances after applying a delta are checked. Landing exactly on the floor is allowed.
Keep state between iterator steps
Use scan to practice an iterator that updates state while producing values.
Its closure receives &mut St for the stored state and one input item,
then returns Option<B> for the next output. In this exercise, both the
state and output value are i64.
Start the adapter with 0i64. Inside the closure, state is an &mut i64:
let next = state.checked_add(d)?;
Method calls can automatically dereference the reference. Here ? returns
None from the closure if addition overflows. This makes the collecting
operation stop while retaining the balances it has already collected.
On a successful addition that meets the floor, update the stored value
with *state = next, then yield Some(next). The * in the assignment
writes through the mutable reference. Assigning state = next would try to
replace a reference with an integer instead.
The starter also writes state + d. That operator does not automatically
convert &mut i64 into the required integer operand. Reading with *state
would fix that type error, but ordinary addition still needs to be replaced
to handle overflow correctly.
Yield the integer, not Some(state). The output is a Vec<i64>, and the
temporary mutable borrow of the adapter's state cannot become its stream
of output values. If collect reports an unexpected item type, trace that
type back to what the closure returns.
None stops this collection, not every future call
Returning Some(next) makes the scan yield a balance. Returning None
makes that call to next() return None, and collect::<Vec<_>>() stops
there. This gives the requested prefix.
A scan is not automatically fused: if you keep it and manually call next()
again after a None, its closure can run again and yield another value.
For example:
let mut steps = [1, 2, 3].into_iter().scan((), |_, n| {
if n == 2 { None } else { Some(n) }
});
assert_eq!(steps.next(), Some(1));
assert_eq!(steps.next(), None);
assert_eq!(steps.next(), Some(3));
Use .fuse() if a reusable iterator must keep returning None after the
first end signal. This function immediately consumes the scan with collect,
so it does not resume after a rejected delta.
Compare the available approaches
scan exposes intermediate outputs as an iterator. It does no per-item
work until consumed. Adding .take(3) before collection limits this pipeline
to at most three scan steps; an earlier stop may produce fewer balances.
fold accumulates a final value. That value can include a vector of running
balances, but a normal fold over the full input does not stop early just
because its accumulator records a rejected delta. try_fold can stop early
and can also carry a vector; it is a valid alternative with a different API.
A direct loop with a mutable balance and break can also implement this
behavior clearly. The advantage of scan here is practicing stateful
composition with other adapters, not avoiding a loop that Clippy inherently
rejects. Output tests alone do not enforce the choice of adapter.
The adapter stores its state between calls and lends it to the closure
mutably for each step. Here that state is an owned integer. Other state
types can contain references or shared handles; using scan does not imply
that everything reachable through its state is unshared.
Passing requires compilation, the expected results and Clippy with warnings treated as errors.
Your code, saved here
Sign in to access your accepted solutions and saved draft history. You can keep reading the lesson without an account.
Sign in to your accountStuck?
Rust reference solution
Sign in to track your attempts toward unlocking the reference solution.