We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Fearless Concurrency: Threads, Channels, Shared State step 5 of 24
Balanced ranges: the arithmetic before the threads
Divide the indices in 0..len into balanced, consecutive ranges. Return
their (start, end) pairs; this function does not create threads.
pub fn balanced_ranges(len: usize, workers: usize) -> Vec<(usize, usize)>
Keeping partitioning separate from thread creation makes it easier to check that each index is assigned exactly once.
Required output
If len == 0 or workers == 0, return an empty vector. In particular,
zero workers means no ranges, even when len is positive.
Otherwise, return exactly min(len, workers) nonempty ranges in order:
- Ranges are half-open:
(2, 5)contains indices2,3, and4. - The first starts at
0, the last ends atlen, and adjacent boundaries match. There are no gaps or overlaps. - Sizes differ by at most one. Put the larger ranges first.
(10, 3) -> [(0, 4), (4, 7), (7, 10)]
(3, 5) -> [(0, 1), (1, 2), (2, 3)]
(7, 7) -> [(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6), (6, 7)]
(0, 4) -> []
(5, 0) -> []
Inputs may have large endpoints, but the number of output ranges is small
enough to fit in memory. Construct the boundaries without iterating over
every index in 0..len.
Spread the remainder over the first ranges
After handling zero inputs, calculate:
base = len / workers
extra = len % workers
Give the first extra ranges base + 1 elements, then give the remaining
ranges base elements. Stop before producing a zero-size range.
With len = 10 and workers = 3, base is 3 and extra is 1, so
the sizes are 4, 3, 3. Keep a running boundary: start at 0, add the
first size to reach 4, then reach 7, then 10.
If workers > len, base is zero and extra equals len. The first
len sizes are one; all later sizes would be zero and must be omitted.
The arithmetic identity len = workers * base + extra explains why the
sizes add up to len. You do not need to evaluate a larger intermediate
expression such as workers * (base + 1).
Why a fixed chunk size gives different results
A fixed size of len.div_ceil(workers) is useful for other partitioning
policies, but it does not always satisfy this exercise's balance rule.
For (10, 3), fixed-size chunking produces sizes 4, 4, 2; the required
sizes are 4, 3, 3. For (17, 8), chunks of size three produce six chunks
with sizes 3, 3, 3, 3, 3, 2. They do not produce two additional empty
chunks. Balanced partitioning instead produces eight ranges with sizes
3, 2, 2, 2, 2, 2, 2, 2.
slice::chunks
takes a chunk size, not a requested number of workers. It yields a shorter
final chunk when needed. Neither strategy guarantees equal execution time:
equal numbers of elements can still require different amounts of work.
Repair both the lint and the algorithm
The starter calculates (len + workers - 1) / workers, which Clippy flags
as manual_div_ceil. That addition can overflow before division. With
overflow checks enabled it can panic; with checks disabled it can wrap and
produce an incorrect size. A size of zero can even leave its loop unable
to advance.
usize::div_ceil
avoids that addition overflow for a nonzero divisor. It still panics for
division by zero. Replacing the expression with div_ceil fixes that
calculation, but does not fix the starter's uneven fixed-size ranges.
Use the quotient-and-remainder plan instead. For each range, compute only
its actual size and add it to the running boundary. These boundaries never
exceed len, so the additions fit in usize, including when the final
endpoint is usize::MAX.
usize is unsigned and its width depends on the target. Do not assume it
is always 64 bits, or that using only addition prevents overflow. The
relevant guarantee here is that every intermediate boundary is bounded
by len.
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.