We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Performance and Data Layout step 8 of 20
Store and transpose a grid in one flat buffer
Store a rectangular grid in one Vec<f32> and transpose it without growing
the output buffer repeatedly. The starter produces the right values, but its
repeated pushes exceed the allocation budget for the large test.
Keep these public fields and implement the supplied methods:
pub struct Grid {
pub data: Vec<f32>,
pub cols: usize,
}
| Method | Required behavior |
|---|---|
new(rows: usize, cols: usize) -> Grid |
Store (r * 1000 + c) as f32 at each zero-based coordinate (r, c), in row order. |
rows(&self) -> usize |
Return data.len() / cols, or zero when cols is zero. |
get(&self, r: usize, c: usize) -> f32 |
Return the stored value at (r, c). |
row(&self, r: usize) -> &[f32] |
Borrow the entire row without copying or allocating. |
transpose(&self) -> Grid |
Return an independent grid whose (c, r) equals the original's (r, c). Leave the original unchanged. |
The grader also constructs a grid directly, so preserve the names and types
of both fields. get, row and transpose must use the stored data, not
recompute the pattern used by new.
For example, transposing data = [1, 2, 3, 4, 5, 6] with cols = 3 gives
data = [1, 4, 2, 5, 3, 6] with cols = 2. The original has two rows;
the result has three.
Index a flat grid
Each row occupies cols consecutive elements. Element (r, c) is at
r * cols + c, and row r is the slice from r * cols up to, but not
including, (r + 1) * cols.
Supplied nonempty grids have a positive column count and a data length
divisible by it. Calls to get and row use valid coordinates. Dimensions,
index arithmetic and the integer expression in new fit in usize, and
buffers fit in memory. Converting a large integer to f32 can round it;
preserve the specified cast rather than changing the value formula.
An empty grid has no valid element or row to read. The two-field representation
cannot retain a nonzero row count when cols is zero: new(3, 0).rows() is
zero. Transpose uses the row count reported by rows() as its new cols.
Reserve space before filling
The harness counts calls to alloc, alloc_zeroed and realloc separately
around construction and transpose. Each operation must use at most one
counted call, including for the 1000 by 1000 test.
Allocate enough space for the whole output before writing it. Two useful
patterns are Vec::with_capacity(rows * cols) followed by pushes, and
vec![0.0; rows * cols] followed by indexed writes. Reserving capacity does
not set the vector's length, so indexing an unfilled reserved vector panics.
Growing from Vec::new() can require several reallocations. Rust does not
promise a particular growth factor, and a reallocation may grow in place
rather than copy the buffer. The exercise's counter is a check on this
toolchain, not a timing benchmark or a portable guarantee about optimizer
behavior. Rust permits allocation calls to be optimized away.
What flat storage changes
A flat vector keeps all stored elements contiguous. A Vec<Vec<f32>> stores
a vector header for each row, with each nonempty row owning its own buffer.
A header contains a pointer, length and capacity; it is not just a pointer.
The row buffers need not be adjacent to one another.
Nested vectors are useful for rows of different lengths. For a rectangular
grid, flat storage avoids separate row buffers and exposes the whole grid
as a slice. Both representations can borrow an individual row as &[f32].
Contiguity can help sequential access and vectorization, but it does not
guarantee either faster code or SIMD instructions. A column traversal jumps
by cols elements in this row-major layout; its cache behavior depends on
the stride and hardware. A tiled transpose can improve locality on large
grids, but is not required here. Measure representative sizes before choosing
a more complicated traversal.
Array of structs or struct of arrays?
For particles with position and mass, an array of structs (AoS) stores
records together in a Vec<Particle>. A struct of arrays (SoA) keeps a
separate vector for each field, such as x, y, z and mass.
AoS keeps one particle's fields close together, which can help a loop that
reads the whole record. SoA puts all x values together, which can help a
loop that reads only x. Neither layout wins for every workload.
SoA also needs a way to keep its vectors the same length and their indices aligned. You can still construct a particle value or a view of its fields; the representation simply requires more care to expose that interface.
Your solution must compile, match the outputs, meet the allocation budget and pass 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.