pub(crate) struct SpanCell {
pub content: String,
pub row_span: u32,
pub col_span: u32,
}
impl SpanCell {
pub(crate) fn new(content: impl Into<String>, row_span: u32, col_span: u32) -> Self {
Self {
content: content.into(),
row_span,
col_span,
}
}
}
pub(crate) fn resolve_span_grid<C>(
rows: &[Vec<C>],
col_span: impl Fn(&C) -> u32,
row_span: impl Fn(&C) -> u32,
mut place: impl FnMut(u32, u32, &C),
) -> u32 {
let mut occupied_until: Vec<u32> = Vec::new();
for (row_idx, row) in rows.iter().enumerate() {
let row_idx = row_idx as u32;
let mut col = 0usize;
for cell in row {
while col < occupied_until.len() && occupied_until[col] > row_idx {
col += 1;
}
let end_row = row_idx + row_span(cell).max(1);
let span = col_span(cell).max(1) as usize;
for c in col..col + span {
if c >= occupied_until.len() {
occupied_until.resize(c + 1, 0);
}
occupied_until[c] = end_row;
}
place(row_idx, col as u32, cell);
col += span;
}
}
occupied_until.len() as u32
}
pub(crate) fn flatten_spanned_rows(rows: &[Vec<SpanCell>]) -> Vec<Vec<String>> {
let mut placed: Vec<(u32, u32, String)> = Vec::new();
let num_cols = resolve_span_grid(
rows,
|c| c.col_span,
|c| c.row_span,
|row_idx, col, cell| placed.push((row_idx, col, cell.content.clone())),
) as usize;
let num_rows = rows.len();
let mut grid = vec![vec![String::new(); num_cols]; num_rows];
for (r, c, content) in placed {
if (r as usize) < num_rows && (c as usize) < num_cols {
grid[r as usize][c as usize] = content;
}
}
grid
}
pub(crate) fn flatten_positioned_cells(
num_rows: usize,
cells: impl Iterator<Item = (u32, u32, u32, String)>,
) -> Vec<Vec<String>> {
let mut rows: Vec<Vec<SpanCell>> = (0..num_rows.max(1)).map(|_| Vec::new()).collect();
for (row, row_span, col_span, content) in cells {
let r = (row as usize).min(rows.len().saturating_sub(1));
rows[r].push(SpanCell::new(content, row_span, col_span));
}
flatten_spanned_rows(&rows)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rowspan_reserves_column() {
let rows = vec![
vec![
SpanCell::new("Alpha", 2, 1),
SpanCell::new("Alice", 1, 1),
SpanCell::new("10", 1, 1),
],
vec![SpanCell::new("Bob", 1, 1), SpanCell::new("20", 1, 1)],
];
let grid = flatten_spanned_rows(&rows);
assert_eq!(grid[0], vec!["Alpha", "Alice", "10"]);
assert_eq!(grid[1], vec!["", "Bob", "20"]);
}
#[test]
fn colspan_widens_grid() {
let rows = vec![
vec![SpanCell::new("Fuse", 1, 2), SpanCell::new("Circuit", 1, 1)],
vec![
SpanCell::new("101", 1, 1),
SpanCell::new("40A", 1, 1),
SpanCell::new("Blower", 1, 1),
],
];
let grid = flatten_spanned_rows(&rows);
assert_eq!(grid[0], vec!["Fuse", "", "Circuit"]);
assert_eq!(grid[1], vec!["101", "40A", "Blower"]);
}
#[test]
fn no_spans_is_identity() {
let rows = vec![
vec![SpanCell::new("a", 1, 1), SpanCell::new("b", 1, 1)],
vec![SpanCell::new("c", 1, 1), SpanCell::new("d", 1, 1)],
];
let grid = flatten_spanned_rows(&rows);
assert_eq!(grid, vec![vec!["a", "b"], vec!["c", "d"]]);
}
}