#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BlockAlloc {
pub rows: usize,
}
pub fn allocate_rows(block_heights: &[usize], budget: usize) -> Vec<BlockAlloc> {
let n = block_heights.len();
let mut out = vec![BlockAlloc { rows: 0 }; n];
if n == 0 {
return out;
}
let total: usize = block_heights.iter().sum();
if total <= budget {
for (i, &h) in block_heights.iter().enumerate() {
out[i].rows = h;
}
return out;
}
if n > budget {
let keep = budget;
let first_kept = n.saturating_sub(keep);
for (i, slot) in out.iter_mut().enumerate() {
slot.rows = if i >= first_kept { 1 } else { 0 };
}
return out;
}
let surplus = budget - n;
let mut remaining = surplus;
for i in (0..n).rev() {
let natural = block_heights[i];
let full_deficit = natural.saturating_sub(1);
if full_deficit > 0 && remaining >= full_deficit {
out[i].rows = natural;
remaining -= full_deficit;
} else if natural >= 2 && remaining >= 1 {
out[i].rows = 2;
remaining -= 1;
} else {
out[i].rows = 1;
}
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn roomy_all_full() {
let heights = [4, 2, 3];
let alloc = allocate_rows(&heights, 12);
assert_eq!(alloc.len(), 3);
assert_eq!(alloc[0].rows, 4);
assert_eq!(alloc[1].rows, 2);
assert_eq!(alloc[2].rows, 3);
let alloc2 = allocate_rows(&[2, 3, 4], 9);
assert_eq!(alloc2[0].rows, 2);
assert_eq!(alloc2[1].rows, 3);
assert_eq!(alloc2[2].rows, 4);
}
#[test]
fn pressure_folds_oldest_first() {
let heights = [5, 4, 3];
let alloc = allocate_rows(&heights, 6);
assert_eq!(alloc[0].rows, 1, "oldest pinned to glyph");
assert_eq!(alloc[1].rows, 2, "mid folded card (1 surplus)");
assert_eq!(alloc[2].rows, 3, "newest full");
let alloc2 = allocate_rows(&heights, 8);
assert_eq!(alloc2[0].rows, 1, "oldest pinned (no surplus left)");
assert_eq!(alloc2[1].rows, 4, "middle full");
assert_eq!(alloc2[2].rows, 3, "newest full");
let alloc3 = allocate_rows(&heights, 7);
assert_eq!(alloc3[0].rows, 2, "oldest folded card");
assert_eq!(alloc3[1].rows, 2, "mid folded card (2-row fold)");
assert_eq!(alloc3[2].rows, 3);
}
#[test]
fn pressure_never_allocates_mid_range_rows() {
let alloc = allocate_rows(&[10], 4);
assert_eq!(alloc[0].rows, 2, "budget 4 of natural 10 folds");
let alloc = allocate_rows(&[10], 9);
assert_eq!(alloc[0].rows, 2);
let alloc = allocate_rows(&[10], 10);
assert_eq!(alloc[0].rows, 10);
for budget in 0..=30usize {
for h in 3..=8usize {
let heights = [h, h, h];
let allocs = allocate_rows(&heights, budget);
let mut sum = 0usize;
for a in &allocs {
assert!(
a.rows <= 2 || a.rows == h,
"mid-range allocation rows={} for natural={h} (budget {budget})",
a.rows
);
sum += a.rows;
}
assert!(
sum <= budget.max(heights.iter().sum()),
"sum {sum} exceeds budget {budget} (heights {heights:?})"
);
}
}
}
#[test]
fn emergency_hides_oldest_and_banners() {
let heights = [2, 3, 4, 5, 6];
let alloc = allocate_rows(&heights, 3);
assert_eq!(alloc.len(), 5);
assert_eq!(alloc[0].rows, 0, "oldest hidden");
assert_eq!(alloc[4].rows, 1, "newest glyph");
assert_eq!(alloc.iter().map(|a| a.rows).sum::<usize>(), 3);
let alloc3 = allocate_rows(&[2, 2, 2, 2], 3);
assert_eq!(alloc3[0].rows, 0);
assert_eq!(alloc3[1].rows, 1);
assert_eq!(alloc3[2].rows, 1);
assert_eq!(alloc3[3].rows, 1);
}
#[test]
fn empty_inputs_no_panic() {
let alloc = allocate_rows(&[], 10);
assert!(alloc.is_empty());
let alloc = allocate_rows(&[5, 4, 3], 0);
assert_eq!(alloc.len(), 3);
assert!(alloc.iter().all(|a| a.rows == 0));
let alloc = allocate_rows(&[], 0);
assert!(alloc.is_empty());
let alloc = allocate_rows(&[0, 0, 0], 10);
assert_eq!(alloc.len(), 3);
assert!(alloc.iter().all(|a| a.rows == 0));
}
#[test]
fn single_block_taller_than_budget_folds() {
let alloc = allocate_rows(&[10], 4);
assert_eq!(alloc[0].rows, 2);
let alloc = allocate_rows(&[10], 2);
assert_eq!(alloc[0].rows, 2);
let alloc = allocate_rows(&[10], 1);
assert_eq!(alloc[0].rows, 1);
let alloc = allocate_rows(&[10], 0);
assert_eq!(alloc[0].rows, 0);
let alloc = allocate_rows(&[10, 10], 3);
assert_eq!(alloc[0].rows, 1);
assert_eq!(alloc[1].rows, 2);
}
}