Shared Rust kernels
catalogue/subtotal
All modules · quoted at commit 9e1884369a67 of DnaCalc/DnaCalc. Comments are the source's own.
held_values · 56 lines · calc-func/src/catalogue/subtotal.rs on GitHub/// The values of the reference arguments `from..`, with the cells `rule` leaves out removed.
fn held_values(
site: &Site<'_>,
a: &[CalcValue],
from: usize,
sv: &mut EvalServices<'_>,
rule: Rule,
) -> Step<Vec<CalcValue>> {
let mut found = Vec::new();
for (index, argument) in a.iter().enumerate().skip(from) {
let CoreRef::Reference(reference) = argument.core_ref() else {
// SUBTOTAL and AGGREGATE's reference form read references only; an array here is
// not modelled.
return Err(site.not_modelled(Some(index)));
};
let needs_knowledge = rule.nested || rule.hidden || rule.filtered;
let mut excluded_cells = BTreeSet::new();
let mut excluded_rows = BTreeSet::new();
if needs_knowledge {
let reader = aggregate::reader(site, sv, index)?;
for &area in reference.areas() {
let Some(SubtotalExclusions {
nested_subtotals,
hidden_rows,
filtered_rows,
}) = reader.subtotal_exclusions(area)
else {
return Err(site.not_modelled(Some(index)));
};
if rule.nested {
excluded_cells.extend(nested_subtotals.iter().map(|k| (k.row(), k.column())));
}
if rule.hidden {
excluded_rows.extend(hidden_rows);
}
if rule.filtered {
excluded_rows.extend(filtered_rows);
}
}
}
let reader = aggregate::reader(site, sv, index)?;
read_reference(site, reader, reference, index, &mut |_, key, value| {
if excluded_rows.contains(&key.row())
|| excluded_cells.contains(&(key.row(), key.column()))
{
return Ok(());
}
if rule.errors && matches!(value.core_ref(), CoreRef::Error(_)) {
return Ok(());
}
found.push(value.clone());
Ok(())
})?;
}
Ok(found)
}
Used by 2 functions.
numbers_of · 12 lines · calc-func/src/catalogue/subtotal.rs on GitHub/// The numbers among held values; the first error is the result.
fn numbers_of(values: &[CalcValue]) -> Step<Vec<f64>> {
let mut found = Vec::new();
for value in values {
match value.core_ref() {
CoreRef::Number(n) => found.push(n),
CoreRef::Error(code) => return fail(code),
_ => {}
}
}
Ok(found)
}
Used by 2 functions.
compute · 80 lines · calc-func/src/catalogue/subtotal.rs on GitHub/// Function `number` (1 to 13, SUBTOTAL's numbering for 1 to 11) over the held values.
fn compute(site: &Site<'_>, number: i64, values: &[CalcValue]) -> Step<CalcValue> {
if number == 3 {
let counted = values
.iter()
.filter(|v| !matches!(v.core_ref(), CoreRef::Empty | CoreRef::Missing))
.count();
return site.number(counted as f64);
}
if number == 2 {
let counted = values
.iter()
.filter(|v| matches!(v.core_ref(), CoreRef::Number(_)))
.count();
return site.number(counted as f64);
}
let numbers = numbers_of(values)?;
let result = match number {
1 => {
let mut total = 0.0;
for n in &numbers {
total = site.add(total, *n)?;
}
site.divide(total, numbers.len() as f64)?
}
4 => numbers.iter().copied().fold(None, |m: Option<f64>, n| Some(m.map_or(n, |m| m.max(n)))).unwrap_or(0.0),
5 => numbers.iter().copied().fold(None, |m: Option<f64>, n| Some(m.map_or(n, |m| m.min(n)))).unwrap_or(0.0),
6 => {
if numbers.is_empty() {
0.0
} else {
let mut product = 1.0;
for n in &numbers {
product = site.multiply(product, *n)?;
}
product
}
}
7 => stdev_from_values(&numbers, VarianceDivisor::Sample).map_err(Exit::Error)?,
8 => stdev_from_values(&numbers, VarianceDivisor::Population).map_err(Exit::Error)?,
9 => {
let mut total = 0.0;
for n in &numbers {
total = site.add(total, *n)?;
}
total
}
10 => variance_from_values(&numbers, VarianceDivisor::Sample).map_err(Exit::Error)?,
11 => variance_from_values(&numbers, VarianceDivisor::Population).map_err(Exit::Error)?,
12 => {
let mut sorted = numbers.clone();
sorted.sort_by(f64::total_cmp);
if sorted.is_empty() {
return fail(WorksheetErrorCode::Num);
}
let mid = sorted.len() / 2;
if sorted.len() % 2 == 1 {
sorted[mid]
} else {
sorted[mid - 1] + (sorted[mid] - sorted[mid - 1]) / 2.0
}
}
13 => {
// MODE.SNGL: the first most frequent number, in reading order.
let mut best: Option<(usize, f64)> = None;
for (i, n) in numbers.iter().enumerate() {
let count = numbers.iter().filter(|m| *m == n).count();
if count >= 2 && best.is_none_or(|(c, _)| count > c) && numbers[..i].iter().all(|m| m != n) {
best = Some((count, *n));
}
}
match best {
Some((_, n)) => n,
None => return fail(WorksheetErrorCode::NA),
}
}
_ => return fail(WorksheetErrorCode::Value),
};
site.number(result)
}
Used by 2 functions.