use super::graph::{Area, Extent};
use super::value::Value;
pub(crate) const SLOTS_A_CELL: u64 = 4;
pub(crate) fn limit(cells: usize, most: u64) -> u64 {
(cells as u64).saturating_mul(SLOTS_A_CELL).min(most)
}
pub(crate) fn fits(extent: Extent, limit: u64) -> bool {
extent.is_some_and(|((top, left), (bottom, right))| {
u64::from(right - left + 1) * u64::from(bottom - top + 1) <= limit
})
}
const NUMBER: u8 = 0;
const OTHER: u8 = 1;
#[inline]
fn number_of(v: &Value) -> (f64, u8) {
match v {
Value::Number(n) => (*n, NUMBER),
_ => (0.0, OTHER),
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct Dense {
top: u32,
left: u32,
width: usize,
height: usize,
values: Vec<Value>,
shadow: Option<Shadow>,
}
#[derive(Clone, Debug, Default, PartialEq)]
struct Shadow {
numbers: Vec<f64>,
kinds: Vec<u8>,
odd: Vec<u32>,
}
impl Dense {
pub(crate) fn over(
extent: Extent,
limit: u64,
old: Option<Dense>,
numbers: bool,
) -> Option<Dense> {
let ((top, left), (bottom, right)) = extent?;
if !fits(extent, limit) {
return None;
}
let width = (right - left + 1) as usize;
let height = (bottom - top + 1) as usize;
let (mut values, spare) = match old {
Some(mut d) if d.stands_over(extent) => {
d.shade(numbers);
return Some(d);
}
Some(d) => (d.values, d.shadow),
None => (Vec::new(), None),
};
values.clear();
values.resize(width * height, Value::Empty);
let mut d = Dense {
top,
left,
width,
height,
values,
shadow: None,
};
if numbers {
d.shadow = Some(d.made(spare.unwrap_or_default()));
}
Some(d)
}
pub(crate) fn shade(&mut self, numbers: bool) {
match (numbers, self.shadow.is_some()) {
(true, false) => self.shadow = Some(self.made(Shadow::default())),
(false, true) => self.shadow = None,
_ => {}
}
}
#[cfg(test)]
pub(crate) fn keeps_numbers(&self) -> bool {
self.shadow.is_some()
}
fn made(&self, mut s: Shadow) -> Shadow {
let n = self.values.len();
s.numbers.clear();
s.numbers.reserve(n);
s.kinds.clear();
s.kinds.reserve(n);
for v in &self.values {
let (x, kind) = number_of(v);
s.numbers.push(x);
s.kinds.push(kind);
}
s.odd.clear();
s.odd.extend(
s.kinds
.chunks(self.width)
.map(|row| row.iter().filter(|&&k| k != NUMBER).count() as u32),
);
s
}
pub(crate) fn stands_over(&self, extent: Extent) -> bool {
extent.is_some_and(|((top, left), (bottom, right))| {
let width = (right - left + 1) as usize;
let height = (bottom - top + 1) as usize;
(self.top, self.left, self.width, self.height) == (top, left, width, height)
})
}
#[cfg(test)]
pub(crate) fn set(&mut self, row: u32, col: u32, v: Value) -> bool {
let Some(i) = self.index(row, col) else {
return false;
};
self.write_at(i, |slot| *slot = v);
true
}
pub fn get(&self, row: u32, col: u32) -> Option<&Value> {
self.index(row, col).map(|i| &self.values[i])
}
pub(crate) fn index(&self, row: u32, col: u32) -> Option<usize> {
let r = row.checked_sub(self.top)? as usize;
let c = col.checked_sub(self.left)? as usize;
(r < self.height && c < self.width).then(|| r * self.width + c)
}
pub(crate) fn slots(&self) -> usize {
self.values.len()
}
pub(crate) fn slot_at(&self, i: usize) -> &Value {
&self.values[i]
}
#[inline]
pub(crate) fn write_at(&mut self, i: usize, f: impl FnOnce(&mut Value)) {
f(&mut self.values[i]);
let Some(s) = self.shadow.as_mut() else {
return;
};
let (n, kind) = number_of(&self.values[i]);
if s.kinds[i] != kind {
let row = i / self.width;
if kind == NUMBER {
s.odd[row] -= 1;
} else {
s.odd[row] += 1;
}
s.kinds[i] = kind;
}
s.numbers[i] = n;
}
pub(crate) fn inside(
&self,
(top, bottom): (u32, u32),
col: u32,
width: usize,
n: usize,
moves: bool,
) -> (usize, usize) {
let (first, last) = (self.top, self.top + (self.height as u32 - 1));
if top < first || bottom > last {
return (0, 0);
}
let (left, right) = (
i64::from(self.left),
i64::from(self.left) + self.width as i64 - 1,
);
let (col, width) = (i64::from(col), width as i64);
if !moves {
let fits = col >= left && col + width - 1 <= right;
return if fits { (0, n) } else { (0, 0) };
}
let a = (left - col).clamp(0, n as i64);
let b = (right - (col + width - 1) + 1).clamp(0, n as i64);
if a >= b {
(0, 0)
} else {
(a as usize, b as usize)
}
}
#[inline]
pub(crate) fn numbers_in_row(&self, row: u32, col: u32, n: usize) -> Option<&[f64]> {
let s = self.shadow.as_ref()?;
let r = row.checked_sub(self.top)? as usize;
let c = col.checked_sub(self.left)? as usize;
if r >= self.height || c + n > self.width {
return None;
}
let i = r * self.width + c;
if s.odd[r] != 0 && s.kinds[i..i + n].iter().any(|&k| k != NUMBER) {
return None;
}
Some(&s.numbers[i..i + n])
}
#[inline]
pub(crate) fn numbers_in_column(
&self,
row: u32,
col: u32,
n: usize,
out: &mut Vec<f64>,
) -> bool {
let Some(s) = self.shadow.as_ref() else {
return false;
};
let (Some(r), Some(c)) = (row.checked_sub(self.top), col.checked_sub(self.left)) else {
return false;
};
let (r, c, w) = (r as usize, c as usize, self.width);
if r + n > self.height || c >= w {
return false;
}
let first = r * w + c;
if s.odd[r..r + n].iter().any(|&o| o != 0)
&& (0..n).any(|j| s.kinds[first + j * w] != NUMBER)
{
return false;
}
out.extend((0..n).map(|j| s.numbers[first + j * w]));
true
}
pub(crate) fn at(&self, i: usize) -> (&Value, (u32, u32)) {
let (r, c) = (i / self.width, i % self.width);
(&self.values[i], (self.top + r as u32, self.left + c as u32))
}
pub fn each<E, F>(&self, area: &Area, mut f: F) -> Result<(), E>
where
F: FnMut(Value) -> Result<(), E>,
{
self.each_ref(area, |v| f(v.clone()))
}
pub(crate) fn each_ref<E, F>(&self, area: &Area, mut f: F) -> Result<(), E>
where
F: FnMut(&Value) -> Result<(), E>,
{
let bottom = self.top + (self.height as u32 - 1);
let right = self.left + (self.width as u32 - 1);
let (top, left) = (area.top.max(self.top), area.left.max(self.left));
let (last_row, last_col) = (area.bottom.min(bottom), area.right.min(right));
if top > last_row || left > last_col {
return Ok(());
}
let n = (last_col - left + 1) as usize;
for row in top..=last_row {
let from = (row - self.top) as usize * self.width + (left - self.left) as usize;
for v in &self.values[from..from + n] {
if !matches!(v, Value::Empty) {
f(v)?;
}
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::calc::ErrorKind;
use crate::sheet::{MAX_COLUMN, MAX_ROW};
fn rect(top: u32, left: u32, bottom: u32, right: u32) -> Area {
Area {
sheet: 0,
top,
left,
bottom,
right,
}
}
fn walk(d: &Dense, area: Area) -> Vec<Value> {
let mut out = Vec::new();
d.each::<(), _>(&area, |v| {
out.push(v);
Ok(())
})
.unwrap();
out
}
#[test]
fn a_store_is_its_extent_row_by_row_and_skips_what_is_empty() {
let mut d = Dense::over(Some(((2, 2), (4, 4))), 9, None, true).unwrap();
let n = Value::Number;
let kinds = [
n(1.0),
Value::Text("x".to_string()),
Value::Bool(true),
n(4.0),
Value::Error(ErrorKind::NA),
n(-0.5),
n(7.0),
n(8.0),
];
let cells = [
(2, 2),
(2, 3),
(2, 4),
(3, 2),
(3, 4),
(4, 2),
(4, 3),
(4, 4),
];
for ((row, col), v) in cells.iter().zip(&kinds) {
assert!(d.set(*row, *col, v.clone()));
}
assert!(!d.set(1, 2, Value::Empty), "above the extent");
assert!(!d.set(2, 5, Value::Empty), "right of it");
assert_eq!(d.get(2, 3), Some(&kinds[1]));
assert_eq!(d.get(3, 3), Some(&Value::Empty), "a cell holding nothing");
assert_eq!(d.get(5, 2), None, "outside");
assert_eq!(d.get(2, 1), None);
assert_eq!(walk(&d, rect(2, 2, 4, 4)), kinds.to_vec());
assert_eq!(walk(&d, rect(1, 1, MAX_ROW, MAX_COLUMN)), kinds.to_vec());
assert_eq!(
walk(&d, rect(3, 3, 9, 9)),
vec![kinds[4].clone(), n(7.0), n(8.0)]
);
assert_eq!(walk(&d, rect(1, 3, 3, 3)), vec![kinds[1].clone()]);
assert_eq!(walk(&d, rect(3, 3, 3, 3)), Vec::<Value>::new());
assert_eq!(walk(&d, rect(5, 1, 9, 9)), Vec::<Value>::new());
assert_eq!(walk(&d, rect(1, 5, 1, 9)), Vec::<Value>::new());
let mut seen = 0;
let stopped = d.each(&rect(2, 2, 4, 4), |v| {
seen += 1;
if matches!(v, Value::Bool(_)) {
Err("stop")
} else {
Ok(())
}
});
assert_eq!((stopped, seen), (Err("stop"), 3));
}
#[test]
fn a_store_keeps_its_memory_and_refuses_what_passes_its_limit() {
let extent = Some(((1, 1), (10, 10)));
let mut d = Dense::over(extent, 100, None, true).unwrap();
assert!(d.set(10, 10, Value::Text("kept".to_string())));
let at = d.values.as_ptr();
let Some(Value::Text(text)) = d.get(10, 10) else {
panic!("a text")
};
let text_at = text.as_ptr();
let same = Dense::over(extent, 100, Some(d), true).unwrap();
assert_eq!(same.values.as_ptr(), at);
let Some(Value::Text(text)) = same.get(10, 10) else {
panic!("the text kept")
};
assert_eq!(text.as_ptr(), text_at, "the same text, not a copy");
let again = Dense::over(Some(((3, 3), (5, 5))), 100, Some(same), true).unwrap();
assert_eq!(again.values.as_ptr(), at, "the old store's memory");
assert_eq!(again.values.len(), 9);
assert!(again.values.iter().all(|v| *v == Value::Empty));
assert_eq!(again.get(3, 3), Some(&Value::Empty));
assert!(Dense::over(Some(((1, 1), (10, 10))), 99, None, true).is_none());
assert!(
Dense::over(None, 100, None, true).is_none(),
"a sheet with no cell"
);
assert!(Dense::over(Some(((1, 1), (MAX_ROW, MAX_COLUMN))), 1 << 20, None, true).is_none());
}
fn slice_of(d: &Dense, row: u32, col: u32, n: usize) -> Option<Vec<f64>> {
(0..n)
.map(|j| match d.get(row, col + j as u32)? {
Value::Number(x) => Some(*x),
_ => None,
})
.collect()
}
#[test]
fn the_cells_a_reference_keeps_inside_are_those_it_leaves_nowhere() {
let d = Dense::over(Some(((2, 2), (6, 6))), 100, None, true).unwrap();
for (top, bottom) in [(2, 2), (3, 5), (1, 3), (6, 7), (2, 6)] {
for col in 0..=8u32 {
for width in 1..=4usize {
for n in 1..=7usize {
for moves in [true, false] {
let fits = |i: usize| {
let left = col + if moves { i as u32 } else { 0 };
let right = left + width as u32 - 1;
top >= 2 && bottom <= 6 && left >= 2 && right <= 6
};
let want: Vec<usize> = (0..n).filter(|&i| fits(i)).collect();
let (a, b) = d.inside((top, bottom), col, width, n, moves);
assert_eq!(
(a..b).collect::<Vec<_>>(),
want,
"rows {top}-{bottom}, column {col}, width {width}, {n} cells, moving {moves}"
);
}
}
}
}
}
}
fn column_of(d: &Dense, row: u32, col: u32, n: usize) -> Option<Vec<f64>> {
(0..n)
.map(|j| match d.get(row + j as u32, col)? {
Value::Number(x) => Some(*x),
_ => None,
})
.collect()
}
fn spelled(s: &Shadow) -> (Vec<u64>, &[u8], &[u32]) {
(
s.numbers.iter().map(|x| x.to_bits()).collect(),
&s.kinds,
&s.odd,
)
}
#[test]
fn a_store_s_numbers_are_its_slots_after_every_write() {
let mut seed: u64 = 0x5EED;
let mut next = |n: u64| {
seed = seed
.wrapping_mul(6_364_136_223_846_793_005)
.wrapping_add(1_442_695_040_888_963_407);
(seed >> 33) % n
};
let values = [
Value::Number(0.0),
Value::Number(-0.0),
Value::Number(1.5),
Value::Number(-3.0),
Value::Number(1e300),
Value::Text("x".to_string()),
Value::Bool(true),
Value::Error(ErrorKind::NA),
Value::Empty,
];
let extents = [Some(((2, 3), (6, 9))), Some(((1, 1), (3, 4)))];
let mut d = Dense::over(extents[0], 100, None, true).unwrap();
let (mut kept, mut columns) = (0, 0);
for round in 0..2000 {
if round % 500 == 499 {
d = Dense::over(extents[next(2) as usize], 100, Some(d), round != 999).unwrap();
} else if round % 100 == 99 {
d.shade(round / 100 % 2 == 1);
}
let (top, left) = (d.top, d.left);
let (row, col) = (
top + next(d.height as u64) as u32,
left + next(d.width as u64) as u32,
);
let v = values[next(values.len() as u64) as usize].clone();
assert!(d.set(row, col, v));
if let Some(s) = &d.shadow {
kept += 1;
for r in 0..d.height {
let row = top + r as u32;
let odd = (0..d.width as u32)
.filter(|&c| !matches!(d.get(row, left + c), Some(Value::Number(_))))
.count();
assert_eq!(s.odd[r] as usize, odd, "round {round}, row {row}");
}
assert_eq!(
spelled(s),
spelled(&d.made(Shadow::default())),
"round {round}"
);
}
for _ in 0..4 {
let row = (top + next(d.height as u64 + 2) as u32).saturating_sub(1);
let col = (left + next(d.width as u64 + 2) as u32).saturating_sub(1);
let n = 1 + next(d.width as u64 + 1) as usize;
let got = d
.numbers_in_row(row, col, n)
.map(|s| s.iter().map(|x| x.to_bits()).collect::<Vec<_>>());
let want = slice_of(&d, row, col, n)
.filter(|_| d.shadow.is_some())
.map(|s| s.iter().map(|x| x.to_bits()).collect::<Vec<_>>());
assert_eq!(got, want, "round {round}: {n} slots from ({row}, {col})");
let n = 1 + next(d.height as u64 + 1) as usize;
let mut out = vec![7.0];
let read = d.numbers_in_column(row, col, n, &mut out);
let want = column_of(&d, row, col, n).filter(|_| d.shadow.is_some());
let got = read.then(|| out[1..].iter().map(|x| x.to_bits()).collect::<Vec<_>>());
assert_eq!(
got,
want.map(|s| s.iter().map(|x| x.to_bits()).collect::<Vec<_>>()),
"round {round}: {n} slots down from ({row}, {col})"
);
assert_eq!(out.len(), if read { 1 + n } else { 1 }, "round {round}");
columns += usize::from(read);
}
}
assert!(
0 < kept && kept < 2000,
"rounds with numbers and without: {kept}"
);
assert!(columns > 0, "a column read whole somewhere");
}
}