We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Collections, Text and First Iterators step 16 of 21
windows and chunks are slice methods, not iterator adapters
Two small slice jobs.
pub fn run_lengths(data: Vec<i64>) -> Vec<(i64, usize)>
pub fn max_window_sum(data: Vec<i64>, k: usize) -> Option<i64>
-
run_lengthsis run-length encoding:[1,1,2,2,1]becomes[(1,2), (2,2), (1,1)]. Note the trailing(1,1)— a run is a run of adjacent equal values, so the two1s at either end are separate runs. -
max_window_sumreturns the largest sum of anykconsecutive elements, orNonewhen there is no such window:k == 0,k > data.len(), or an empty slice.
The error that names the boundary
The starter writes what reads like perfectly ordinary iterator code:
data.iter().windows(k).map(...)
error[E0599]: no method named `windows` found for struct
`std::slice::Iter<'a, T>` in the current scope
Read the type in that message: std::slice::Iter. You called .iter(),
which turned your slice into an iterator, and then asked the iterator for
a slice method. windows, chunks, chunks_exact, chunk_by, split_at,
sort, binary_search, first, last — all of these live on [T], and
none of them exist on Iterator.
The fix is to delete .iter(). Vec<T> derefs to [T], so
data.windows(k) works directly, and it returns an iterator — of slices —
which you can then .map() over as usual. Slice method first, iterator
second.
Why the boundary exists at all
It is not an oversight, and knowing the reason means you will never have to memorise the list.
An Iterator is a one-way, one-shot protocol: it has exactly one method,
next, and once an item is handed out it is gone. windows(3) needs to
return [a,b,c], then [b,c,d] — it must look at b and c again.
There is no way to do that over a one-way stream without buffering, and
buffering would mean allocating, and an adapter that silently allocates is
not something std will give you. A slice, by contrast, is random-access
memory you already hold, so windows is just arithmetic on indices and each
yielded item is a borrow, not a copy.
The same logic explains sort (needs to move elements around), and
binary_search (needs to jump).
If you genuinely have an iterator and want pairs of neighbours, the std-only trick is to zip a stream with itself, offset by one:
v.iter().zip(v.iter().skip(1)) // (1,2), (2,3), (3,4), (4,5)
That gives you windows of exactly 2, lazily, with no buffering — which is
most of what people want windows for anyway.
windows(0) panics
thread 'main' panicked at 'window size must be non-zero'
Not None, not an empty iterator — a panic. There is no sensible sequence of
zero-width windows, and std takes the position that asking for one is a bug
in the caller rather than a runtime condition. Your guard is not defensive
padding; it is part of the specification, and there is a test case for it.
This is the same design you met with BTreeMap::range earlier. Build the
habit of asking, for any std method that takes a size or a range, “what does
it do at the boundary?” — the answer is in the docs under Panics, and it
is worth two seconds before you write the call rather than two hours after
the incident.
chunks versus chunks_exact versus chunk_by
Three neighbours that do different things:
let v = [1, 2, 3, 4, 5];
v.chunks(2) // [1,2] [3,4] [5] ragged tail included
v.chunks_exact(2) // [1,2] [3,4] tail dropped...
// ...and available via .remainder() -> [5]
v.windows(2) // [1,2] [2,3] [3,4] [4,5] overlapping
chunks partitions with a possibly-short last piece; chunks_exact gives
only full pieces and hands the leftovers back separately, which lets the
optimiser vectorise the body because every chunk has the same length;
windows overlaps and never has a short one.
chunk_by is the fourth: it groups adjacent elements by a predicate on
each neighbouring pair, so the chunk boundaries come from the data rather than
from a fixed size. That is exactly run-length encoding:
data.chunk_by(|a, b| a == b)
One caution when you search for it: chunk_by was called group_by until
Rust 1.77. Every blog post and Stack Overflow answer written before that says
group_by, and the rename happened because group_by sounded like SQL’s
arbitrary-key grouping, which — as the previous problem in this track showed
— is a completely different operation needing a map.
::: question max_window_sum as written is O(n·k). Could you do better, and should you here?
Yes: a sliding window keeps a running sum, adds the entering element and
subtracts the leaving one, which is O(n) regardless of k. With
windows, you re-add all k elements for every position.
Should you? For this problem, no — the inputs are tiny and the windows
version says what it means in one line. But it is worth knowing the shape,
because the O(n) version is not harder:
let mut sum: i64 = data[..k].iter().sum();
let mut best = sum;
for i in k..data.len() {
sum += data[i] - data[i - k];
best = best.max(sum);
}
And it is worth noticing what you gave up: that version indexes, so it can
panic; the windows version cannot index out of bounds at all. Clarity and
safety on one side, a factor of k on the other. Choose deliberately rather
than by reflex.
:::
Remember the grade is compile + tests + clippy -D warnings.
Loading visualization…
Stuck?
Rust reference solution
Sign in to attempt this problem and reveal the reference solution.