Shared Rust kernels
catalogue/grouping
All modules · quoted at commit 9e1884369a67 of DnaCalc/DnaCalc. Comments are the source's own.
Table::height · 3 lines · calc-func/src/catalogue/grouping.rs on GitHubfn height(&self) -> usize {
self.rows.len()
}
Used by 2 functions.
table · 21 lines · calc-func/src/catalogue/grouping.rs on GitHubfn table(site: &Site<'_>, a: &[CalcValue], index: usize, sv: &mut EvalServices<'_>) -> Step<Table> {
let (rows, cols, cells) = match a[index].core_ref() {
CoreRef::Reference(_) => {
let g = grid(site, a, index, sv)?;
(g.rows, g.cols, g.cells)
}
CoreRef::Array(array) => {
let shape = array.shape();
(shape.rows, shape.cols, array.iter_row_major().cloned().collect())
}
CoreRef::Error(code) => return fail(code),
_ => (1, 1, vec![a[index].clone()]),
};
if rows == 0 || cols == 0 {
return fail(W::Value);
}
Ok(Table {
rows: cells.chunks(cols).map(<[CalcValue]>::to_vec).collect(),
cols,
})
}
Used by 2 functions.
functions · 17 lines · calc-func/src/catalogue/grouping.rs on GitHub/// The callables of the function argument: one function value or LAMBDA, or a row of them.
fn functions(a: &[CalcValue], index: usize) -> Step<Vec<CalcValue>> {
let value = &a[index];
if value.callable_value().is_some() {
return Ok(vec![value.clone()]);
}
if let CoreRef::Array(array) = value.core_ref() {
let all: Vec<CalcValue> = array.iter_row_major().cloned().collect();
if !all.is_empty() && all.iter().all(|f| f.callable_value().is_some()) {
return Ok(all);
}
}
match value.core_ref() {
CoreRef::Error(code) => fail(code),
_ => fail(W::Value),
}
}
Used by 2 functions.
whole · 14 lines · calc-func/src/catalogue/grouping.rs on GitHub/// An optional whole-number argument: `None` omitted, #VALUE! when not a whole number.
fn whole(site: &Site<'_>, a: &[CalcValue], index: usize) -> Step<Option<i64>> {
match a.get(index) {
None => Ok(None),
Some(value) if value.is_missing() || matches!(value.core_ref(), CoreRef::Empty) => Ok(None),
Some(value) => {
let n = site.number_argument(index, value)?;
if n.fract() != 0.0 || !n.is_finite() {
return fail(W::Value);
}
Ok(Some(n as i64))
}
}
}
Used by 2 functions.
sort_entries · 26 lines · calc-func/src/catalogue/grouping.rs on GitHub/// The sort entries of argument `index`: whole, non-zero numbers.
fn sort_entries(site: &Site<'_>, a: &[CalcValue], index: usize) -> Step<Vec<i64>> {
let Some(value) = a.get(index) else {
return Ok(Vec::new());
};
if value.is_missing() {
return Ok(Vec::new());
}
let cells: Vec<CalcValue> = match value.core_ref() {
CoreRef::Array(array) => array.iter_row_major().cloned().collect(),
_ => vec![value.clone()],
};
let mut out = Vec::new();
for cell in cells {
let n = match cell.core_ref() {
CoreRef::Number(n) => n,
CoreRef::Error(code) => return fail(code),
_ => site.number_argument(index, &cell)?,
};
if n == 0.0 || n.fract() != 0.0 || !n.is_finite() {
return fail(W::Value);
}
out.push(n as i64);
}
Ok(out)
}
Used by 2 functions.
filter · 45 lines · calc-func/src/catalogue/grouping.rs on GitHub/// The rows the filter keeps, of `count` data rows (`all` rows with the header row).
fn filter(
site: &Site<'_>,
a: &[CalcValue],
index: usize,
count: usize,
all: usize,
sv: &mut EvalServices<'_>,
) -> Step<Vec<bool>> {
let Some(value) = a.get(index) else {
return Ok(vec![true; count]);
};
if value.is_missing() {
return Ok(vec![true; count]);
}
let t = table(site, a, index, sv)?;
let cells: Vec<CalcValue> = if t.cols == 1 {
t.rows.into_iter().map(|mut row| row.remove(0)).collect()
} else if t.height() == 1 {
t.rows.into_iter().next().unwrap_or_default()
} else {
return fail(W::Value);
};
let skip = if cells.len() == count {
0
} else if cells.len() == all {
all - count
} else {
return fail(W::Value);
};
let mut keep = Vec::with_capacity(count);
for cell in &cells[skip..] {
keep.push(match cell.core_ref() {
CoreRef::Logical(b) => b,
CoreRef::Number(n) => n != 0.0,
CoreRef::Empty | CoreRef::Missing => false,
CoreRef::Error(code) => return fail(code),
_ => return fail(W::Value),
});
}
if !keep.iter().any(|k| *k) {
return fail(W::Value);
}
Ok(keep)
}
Used by 2 functions.
kind · 10 lines · calc-func/src/catalogue/grouping.rs on GitHub/// The rank of a field's kind in the group order: numbers, text, logicals, errors, blanks.
fn kind(value: &CalcValue) -> u8 {
match value.core_ref() {
CoreRef::Number(_) => 0,
CoreRef::Text(_) => 1,
CoreRef::Logical(_) => 2,
CoreRef::Error(_) => 3,
_ => 4,
}
}
Used by 2 functions.
order · 12 lines · calc-func/src/catalogue/grouping.rs on GitHubfn order(site: &Site<'_>, x: &CalcValue, y: &CalcValue) -> Step<Ordering> {
match (x.core_ref(), y.core_ref()) {
(CoreRef::Number(a), CoreRef::Number(b)) => comparison::numbers(site, a, b),
(CoreRef::Text(a), CoreRef::Text(b)) => {
comparison::texts(a.utf16_code_units(), b.utf16_code_units(), Want::Order)
.map_err(|_| site.not_modelled(None))
}
(CoreRef::Logical(a), CoreRef::Logical(b)) => Ok(a.cmp(&b)),
(CoreRef::Error(a), CoreRef::Error(b)) => Ok((a as u8).cmp(&(b as u8))),
_ => Ok(kind(x).cmp(&kind(y))),
}
}
Used by 2 functions.
same · 10 lines · calc-func/src/catalogue/grouping.rs on GitHubfn same(site: &Site<'_>, x: &CalcValue, y: &CalcValue) -> Step<bool> {
match (x.core_ref(), y.core_ref()) {
(CoreRef::Text(a), CoreRef::Text(b)) => {
comparison::texts(a.utf16_code_units(), b.utf16_code_units(), Want::Equality)
.map(Ordering::is_eq)
.map_err(|_| site.not_modelled(None))
}
_ => order(site, x, y).map(Ordering::is_eq),
}
}
Used by 2 functions.
canonical · 24 lines · calc-func/src/catalogue/grouping.rs on GitHub/// `keys` with each field shown as the first spelling met of the values equal to it in its
/// column (`east` shown as `East` where `East` comes first), as Excel shows a group's fields.
fn canonical(site: &Site<'_>, keys: &[Vec<CalcValue>], rows: &[usize]) -> Step<Vec<Vec<CalcValue>>> {
let mut out = keys.to_vec();
let width = keys.first().map_or(0, Vec::len);
for column in 0..width {
let mut seen: Vec<CalcValue> = Vec::new();
for &row in rows {
let value = &keys[row][column];
let mut shown = None;
for first in &seen {
if same(site, first, value)? {
shown = Some(first.clone());
break;
}
}
match shown {
Some(first) => out[row][column] = first,
None => seen.push(value.clone()),
}
}
}
Ok(out)
}
Used by 2 functions.
group · 27 lines · calc-func/src/catalogue/grouping.rs on GitHub/// The groups of `keys` (one key row per data row, among `rows`): each the first key met and the
/// rows that share it, in the order first met.
fn group(site: &Site<'_>, keys: &[Vec<CalcValue>], rows: &[usize]) -> Step<Vec<(Vec<CalcValue>, Vec<usize>)>> {
let mut groups: Vec<(Vec<CalcValue>, Vec<usize>)> = Vec::new();
for &row in rows {
let key = &keys[row];
let mut found = None;
for (at, (first, _)) in groups.iter().enumerate() {
let mut equal = true;
for (x, y) in key.iter().zip(first) {
if !same(site, x, y)? {
equal = false;
break;
}
}
if equal {
found = Some(at);
break;
}
}
match found {
Some(at) => groups[at].1.push(row),
None => groups.push((key.clone(), vec![row])),
}
}
Ok(groups)
}
Used by 2 functions.
call · 7 lines · calc-func/src/catalogue/grouping.rs on GitHub/// Calls `f` through the services' invoker.
fn call(site: &Site<'_>, sv: &mut EvalServices<'_>, f: &CalcValue, args: &[CalcValue]) -> Step<CalcValue> {
let Some(invoker) = sv.callables() else {
return Err(site.not_modelled(None));
};
invoker.call(f, args).map_err(|CallAbandoned| site.not_modelled(None))
}
Used by 2 functions.
column · 7 lines · calc-func/src/catalogue/grouping.rs on GitHubfn column(values: Vec<CalcValue>) -> CalcValue {
let rows = values.len().max(1);
let cells = if values.is_empty() { vec![CalcValue::empty()] } else { values };
CalcValue::array(
CalcArray::new(ArrayShape { rows, cols: 1 }, cells).expect("a column of its own length"),
)
}
Used by 2 functions.
aggregate · 36 lines · calc-func/src/catalogue/grouping.rs on GitHub/// One aggregate: `f` called with the values of `column_index` of `rows`, and, when `f` takes two
/// arguments, those of `relative`; an array result is kept as the cell's value, a reference
/// result is #VALUE!.
fn aggregate(
site: &Site<'_>,
sv: &mut EvalServices<'_>,
f: &CalcValue,
values: &Table,
column_index: usize,
rows: &[usize],
relative: &[usize],
) -> Step<CalcValue> {
let pick = |rows: &[usize]| -> Vec<CalcValue> {
rows.iter()
.map(|&r| match values.rows[r][column_index].core_ref() {
CoreRef::Missing => CalcValue::empty(),
_ => values.rows[r][column_index].clone(),
})
.collect()
};
let two = f.callable_value().is_some_and(|c| c.arity().min >= 2);
let mut args = vec![column(pick(rows))];
if two {
args.push(column(pick(relative)));
}
let result = call(site, sv, f, &args)?;
if result.callable_value().is_some() {
return Ok(CalcValue::error(W::Calc));
}
Ok(match result.core_ref() {
CoreRef::Array(array) => CalcValue::array(array.clone()),
CoreRef::Reference(_) => CalcValue::error(W::Value),
CoreRef::Missing => CalcValue::empty(),
_ => result,
})
}
Used by 2 functions.
text · 3 lines · calc-func/src/catalogue/grouping.rs on GitHubfn text(s: &str) -> CalcValue {
CalcValue::text(ExcelText::from_interop_assignment(s))
}
Used by 2 functions.
headers · 17 lines · calc-func/src/catalogue/grouping.rs on GitHub/// What the header argument says: whether the tables hold a header row, and whether the result
/// shows headers.
fn headers(site: &Site<'_>, a: &[CalcValue], index: usize, values: &Table) -> Step<(bool, bool)> {
match whole(site, a, index)? {
None => {
let detected = values.height() >= 2
&& matches!(values.rows[0][0].core_ref(), CoreRef::Text(_))
&& matches!(values.rows[1][0].core_ref(), CoreRef::Number(_));
Ok((detected, false))
}
Some(0) => Ok((false, false)),
Some(1) => Ok((true, false)),
Some(2) => Ok((false, true)),
Some(3) => Ok((true, true)),
Some(_) => fail(W::Value),
}
}
Used by 2 functions.
pairs · 12 lines · calc-func/src/catalogue/grouping.rs on GitHub/// The (value column, function) pairs the aggregates take.
fn pairs(functions: &[CalcValue], value_cols: usize) -> Step<Vec<(usize, usize)>> {
if functions.len() == 1 {
Ok((0..value_cols).map(|v| (v, 0)).collect())
} else if value_cols == 1 {
Ok((0..functions.len()).map(|f| (0, f)).collect())
} else if value_cols == functions.len() {
Ok((0..value_cols).map(|i| (i, i)).collect())
} else {
fail(W::Value)
}
}
Used by 2 functions.
function_name · 4 lines · calc-func/src/catalogue/grouping.rs on GitHub/// A function's name as a header shows it.
fn function_name(f: &CalcValue) -> CalcValue {
text(f.callable_value().map(|c| c.summary()).unwrap_or(""))
}
Used by 2 functions.
sort_groups · 65 lines · calc-func/src/catalogue/grouping.rs on GitHub/// The groups sorted: each row field ascending unless `entries` turn it, with the hierarchy kept
/// (`tabular` false) or in the entries' order (`tabular` true); an entry past the fields sorts by
/// that aggregate.
fn sort_groups(
site: &Site<'_>,
groups: &mut [(Vec<CalcValue>, Vec<usize>, Vec<CalcValue>)],
fields: usize,
entries: &[i64],
tabular: bool,
) -> Step<()> {
// (column, descending), in priority order.
let mut keys: Vec<(usize, bool)> = Vec::new();
let direction = |column: usize| {
entries
.iter()
.find(|e| e.unsigned_abs() as usize == column + 1)
.is_some_and(|e| *e < 0)
};
let value_sort = entries.iter().find(|e| e.unsigned_abs() as usize > fields);
if tabular {
for e in entries {
keys.push(((e.unsigned_abs() - 1) as usize, *e < 0));
}
for c in 0..fields {
if !keys.iter().any(|(k, _)| *k == c) {
keys.push((c, false));
}
}
} else {
for c in 0..fields {
if c + 1 == fields
&& let Some(e) = value_sort
{
keys.push(((e.unsigned_abs() - 1) as usize, *e < 0));
}
keys.push((c, direction(c)));
}
}
let mut refused: Option<Exit> = None;
groups.sort_by(|x, y| {
if refused.is_some() {
return Ordering::Equal;
}
for &(column, descending) in &keys {
let (u, v) = if column < fields {
(&x.0[column], &y.0[column])
} else {
(&x.2[column - fields], &y.2[column - fields])
};
match order(site, u, v) {
Ok(Ordering::Equal) => continue,
Ok(o) => return if descending { o.reverse() } else { o },
Err(exit) => {
refused = Some(exit);
return Ordering::Equal;
}
}
}
Ordering::Equal
});
match refused {
Some(exit) => Err(exit),
None => Ok(()),
}
}
Used by 2 functions.
result · 8 lines · calc-func/src/catalogue/grouping.rs on GitHubfn result(rows: Vec<Vec<CalcValue>>) -> Step<CalcValue> {
if rows.len() == 1 && rows[0].len() == 1 {
return Ok(rows.into_iter().next().unwrap().remove(0));
}
Ok(CalcValue::array(
CalcArray::from_rows(rows).ok_or(Exit::Error(W::Value))?,
))
}
Used by 2 functions.