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 GitHub
fn height(&self) -> usize {
    self.rows.len()
}

Used by 2 functions.

table · 21 lines · calc-func/src/catalogue/grouping.rs on GitHub
fn 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 GitHub
fn 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 GitHub
fn 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 GitHub
fn 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 GitHub
fn 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 GitHub
fn 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.