From: Ben Pfaff Date: Fri, 11 Apr 2025 15:42:11 +0000 (-0700) Subject: More tests X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=106ad5f480824123f5247bcb0af619a51402a4f8;p=pspp More tests --- diff --git a/rust/pspp/src/format/mod.rs b/rust/pspp/src/format/mod.rs index 14a6209de1..3b87aa4664 100644 --- a/rust/pspp/src/format/mod.rs +++ b/rust/pspp/src/format/mod.rs @@ -471,6 +471,30 @@ impl Format { d: 0, }; + pub const F40_1: Format = Format { + type_: Type::F, + w: 40, + d: 1, + }; + + pub const F40_2: Format = Format { + type_: Type::F, + w: 40, + d: 2, + }; + + pub const F40_3: Format = Format { + type_: Type::F, + w: 40, + d: 3, + }; + + pub const PCT40_1: Format = Format { + type_: Type::Pct, + w: 40, + d: 1, + }; + pub const F8_2: Format = Format { type_: Type::F, w: 8, diff --git a/rust/pspp/src/output/pivot/mod.rs b/rust/pspp/src/output/pivot/mod.rs index d629cc29e0..ecf71afeab 100644 --- a/rust/pspp/src/output/pivot/mod.rs +++ b/rust/pspp/src/output/pivot/mod.rs @@ -26,34 +26,7 @@ //! a category for each dimension to a value, which is commonly a number but //! could also be a variable name or an arbitrary text string. //! -//! Creating a pivot table usually consists of the following steps: -//! -//! 1. Create the table with pivot_table_create(), passing in the title. -//! -//! 2. Optionally, set the format to use for "count" values with -//! pivot_table_set_weight_var() or pivot_table_set_weight_format(). -//! -//! 3. Create each dimension with pivot_dimension_create() and populate it with -//! categories and, possibly, with groups that contain the categories. This -//! call also assigns the dimension to an axis. -//! -//! In simple cases, only a call to pivot_dimension_create() is needed. -//! Other functions such as pivot_category_create_group() can be used for -//! hierarchies of categories. -//! -//! Sometimes it's easier to create categories in tandem with inserting data, -//! for example by adding a category for a variable just before inserting the -//! first cell for that variable. In that case, creating categories and -//! inserting data can be interleaved. -//! -//! 4. Insert data. For each cell, supply the category indexes, which are -//! assigned starting from 0 in the order in which the categories were -//! created in step 2, and the value to go in the cell. If the table has a -//! small, fixed number of dimensions, functions like, e.g. -//! pivot_table_put3() for 3 dimensions, can be used. The general function -//! pivot_table_put() works for other cases. -//! -//! 5. Output the table for user consumption. Use pivot_table_submit(). +//! Use [PivotTable::builder] to create a pivot table. use std::{ collections::HashMap, @@ -298,7 +271,7 @@ impl Iterator for AxisIterator { } impl PivotTable { - fn builder(title: impl Into, dimensions: &[DimensionBuilder]) -> PivotTableBuilder { + fn builder(title: impl Into, dimensions: Vec) -> PivotTableBuilder { PivotTableBuilder::new(title, dimensions) } fn axis_values(&self, axis: Axis3) -> AxisIterator { @@ -395,34 +368,29 @@ impl Group { #[derive(Clone)] pub struct DimensionBuilder { axis: Axis3, - root: GroupBuilder, - len: usize, - hide_all_labels: bool, + dimension: Dimension, } impl DimensionBuilder { pub fn new(axis: Axis3, root: GroupBuilder) -> Self { - let len = root.len(); + let mut leaves = Vec::with_capacity(root.len()); + let root = root.build(None, &mut leaves); Self { axis, - root, - len, - hide_all_labels: false, + dimension: Dimension { + root, + presentation_order: (0..leaves.len()).collect(), + data_leaves: leaves, + hide_all_labels: false, + }, } } pub fn with_all_labels_hidden(mut self) -> Self { - self.hide_all_labels = true; + self.dimension.hide_all_labels = true; self } fn build(self) -> Dimension { - let mut leaves = Vec::with_capacity(self.len); - let root = self.root.build(None, &mut leaves); - Dimension { - root, - presentation_order: (0..leaves.len()).collect(), - data_leaves: leaves, - hide_all_labels: self.hide_all_labels, - } + self.dimension } } @@ -551,16 +519,26 @@ impl From<&str> for CategoryBuilder { pub struct PivotTableBuilder { look: Arc, title: Box, - dimensions: Vec, + axes: EnumMap, + dimensions: Vec, cells: HashMap, } impl PivotTableBuilder { - pub fn new(title: impl Into, dimensions: &[DimensionBuilder]) -> Self { + pub fn new(title: impl Into, dimension_builders: Vec) -> Self { + let mut dimensions = Vec::with_capacity(dimension_builders.len()); + let mut axes = EnumMap::from_fn(|_axis| Axis { + dimensions: Vec::with_capacity(dimension_builders.len()), + }); + for d in dimension_builders { + axes[d.axis].dimensions.push(dimensions.len()); + dimensions.push(d.build()); + } Self { look: Settings::global().look.clone(), title: Box::new(title.into()), - dimensions: dimensions.to_vec(), + axes, + dimensions, cells: HashMap::new(), } } @@ -568,24 +546,36 @@ impl PivotTableBuilder { self.look = look; self } + pub fn insert_number(&mut self, data_indexes: &[usize], number: Option, class: Class) { + let format = match class { + Class::Other => Settings::global().default_format, + Class::Integer => Format::F40, + Class::Correlations => Format::F40_3, + Class::Significance => Format::F40_3, + Class::Percent => Format::PCT40_1, + Class::Residual => Format::F40_2, + Class::Count => Format::F40, // XXX + }; + let value = Value::new(ValueInner::Number { + show: None, + format, + honor_small: class == Class::Other, + value: number, + var_name: None, + value_label: None, + }); + self.insert(data_indexes, value); + } pub fn insert(&mut self, data_indexes: &[usize], value: Value) { self.cells.insert( - cell_index(data_indexes, self.dimensions.iter().map(|d| d.len)), + cell_index(data_indexes, self.dimensions.iter().map(|d| d.len())), value, ); } pub fn build(self) -> PivotTable { let mut table = PivotTable::new(self.title, self.look.clone()); - let mut dimensions = Vec::with_capacity(self.dimensions.len()); - let mut axes = EnumMap::from_fn(|_axis| Axis { - dimensions: Vec::with_capacity(self.dimensions.len()), - }); - for d in self.dimensions { - axes[d.axis].dimensions.push(dimensions.len()); - dimensions.push(d.build()); - } - table.dimensions = dimensions; - table.axes = axes; + table.dimensions = self.dimensions; + table.axes = self.axes; table.cells = self.cells; table.current_layer = repeat_n(0, table.axes[Axis3::Z].dimensions.len()).collect(); table diff --git a/rust/pspp/src/output/pivot/test.rs b/rust/pspp/src/output/pivot/test.rs index b683992ae4..b4748c007c 100644 --- a/rust/pspp/src/output/pivot/test.rs +++ b/rust/pspp/src/output/pivot/test.rs @@ -3,8 +3,8 @@ use std::sync::Arc; use enum_map::EnumMap; use crate::output::pivot::{ - Area, Axis2, Border, BorderStyle, Color, Dimension, Group, HeadingRegion, LabelPosition, Look, - PivotTable, RowColBorder, Stroke, + Area, Axis2, Border, BorderStyle, Class, Color, Dimension, Group, HeadingRegion, LabelPosition, + Look, PivotTable, RowColBorder, Stroke, }; use super::{Axis3, DimensionBuilder, GroupBuilder, PivotTableBuilder, Value}; @@ -28,7 +28,7 @@ fn d1(title: &str, axis: Axis3) -> PivotTable { group.push(Value::new_text(name)); } let dimension = DimensionBuilder::new(axis, group); - let mut pt = PivotTableBuilder::new(Value::new_text(title), &[dimension]); + let mut pt = PivotTableBuilder::new(Value::new_text(title), vec![dimension]); for i in 0..3 { pt.insert(&[i], Value::new_integer(Some(i as f64))); } @@ -89,7 +89,7 @@ fn d2(title: &str, axes: [Axis3; 2], dimension_labels: Option) -> } let d2 = DimensionBuilder::new(axes[1], b); - let mut pt = PivotTableBuilder::new(Value::new_text(title), &[d1, d2]); + let mut pt = PivotTableBuilder::new(Value::new_text(title), vec![d1, d2]); let mut i = 0; for b in 0..3 { for a in 0..3 { @@ -544,7 +544,7 @@ Caption #[test] fn no_dimension() { - let pivot_table = PivotTableBuilder::new(Value::new_text("No Dimensions"), &[]) + let pivot_table = PivotTableBuilder::new(Value::new_text("No Dimensions"), vec![]) .with_look(Arc::new(test_look())) .build(); assert_rendering( @@ -561,7 +561,7 @@ fn empty_dimensions() { let look = Arc::new(test_look().with_omit_empty(false)); let d1 = DimensionBuilder::new(Axis3::X, GroupBuilder::new(Value::new_text("a"))); - let pivot_table = PivotTableBuilder::new(Value::new_text("One Empty Dimension"), &[d1]) + let pivot_table = PivotTableBuilder::new(Value::new_text("One Empty Dimension"), vec![d1]) .with_look(look.clone()) .build(); assert_rendering(&pivot_table, "One Empty Dimension\n"); @@ -571,7 +571,7 @@ fn empty_dimensions() { Axis3::X, GroupBuilder::new(Value::new_text("b")).with_label_shown(), ); - let pivot_table = PivotTableBuilder::new(Value::new_text("Two Empty Dimensions"), &[d1, d2]) + let pivot_table = PivotTableBuilder::new(Value::new_text("Two Empty Dimensions"), vec![d1, d2]) .with_look(look.clone()) .build(); assert_rendering(&pivot_table, "Two Empty Dimensions\n"); @@ -589,7 +589,7 @@ fn empty_dimensions() { ); let pivot_table = PivotTableBuilder::new( Value::new_text("Three Dimensions, Two Empty"), - &[d1, d2, d3], + vec![d1, d2, d3], ) .with_look(look.clone()) .build(); @@ -610,7 +610,7 @@ fn empty_groups() { .with(Value::new_text("b3")); let d2 = DimensionBuilder::new(Axis3::Y, b); - let mut pt = PivotTableBuilder::new(Value::new_text("Empty Groups"), &[d1, d2]); + let mut pt = PivotTableBuilder::new(Value::new_text("Empty Groups"), vec![d1, d2]); let mut i = 0; for b in 0..2 { for a in 0..2 { @@ -668,7 +668,7 @@ fn d4( .with(Group::builder("dg1").with("d1").with("d2")) .with("d3"), ); - let mut pivot_table = PivotTable::builder(title, &[a, b, c, d]) + let mut pivot_table = PivotTable::builder(title, vec![a, b, c, d]) .with_look(Arc::new(test_look().with_borders(borders))); let mut i = 0; for d in 0..3 { @@ -976,3 +976,104 @@ dg1┊d1│ c1 0│ 1┊ 2│ 3│ 4┊ 5│ 6│ 7┊ 8 ", ); } + +#[test] +fn small_numbers() { + let exponent = Dimension::builder( + Axis3::Y, + Group::builder("exponent") + .with("0") + .with("-1") + .with("-2") + .with("-3") + .with("-4") + .with("-5") + .with("-6") + .with("-7") + .with("-8") + .with("-9") + .with_label_shown(), + ); + let sign = Dimension::builder( + Axis3::X, + Group::builder("sign") + .with("positive") + .with("negative") + .with_label_shown(), + ); + let rc = Dimension::builder( + Axis3::X, + Group::builder("result class") + .with((Value::new_text("general"), Class::Other)) + .with((Value::new_text("specific"), Class::Residual)) + .with_label_shown(), + ); + let mut pt = PivotTable::builder("small numbers", vec![exponent, sign, rc]); + pt.insert_number(&[0, 0, 0], Some(1.0), Class::Other); + pt.insert_number(&[1, 0, 0], Some(0.1), Class::Other); + pt.insert_number(&[2, 0, 0], Some(0.01), Class::Other); + pt.insert_number(&[3, 0, 0], Some(0.001), Class::Other); + pt.insert_number(&[4, 0, 0], Some(0.0001), Class::Other); + pt.insert_number(&[5, 0, 0], Some(0.00001), Class::Other); + pt.insert_number(&[6, 0, 0], Some(0.000001), Class::Other); + pt.insert_number(&[7, 0, 0], Some(0.0000001), Class::Other); + pt.insert_number(&[8, 0, 0], Some(0.00000001), Class::Other); + pt.insert_number(&[9, 0, 0], Some(0.000000001), Class::Other); + pt.insert_number(&[0, 0, 1], Some(-1.0), Class::Residual); + pt.insert_number(&[1, 0, 1], Some(-0.1), Class::Residual); + pt.insert_number(&[2, 0, 1], Some(-0.01), Class::Residual); + pt.insert_number(&[3, 0, 1], Some(-0.001), Class::Residual); + pt.insert_number(&[4, 0, 1], Some(-0.0001), Class::Residual); + pt.insert_number(&[5, 0, 1], Some(-0.00001), Class::Residual); + pt.insert_number(&[6, 0, 1], Some(-0.000001), Class::Residual); + pt.insert_number(&[7, 0, 1], Some(-0.0000001), Class::Residual); + pt.insert_number(&[8, 0, 1], Some(-0.00000001), Class::Residual); + pt.insert_number(&[9, 0, 1], Some(-0.000000001), Class::Residual); + pt.insert_number(&[0, 1, 0], Some(1.0), Class::Other); + pt.insert_number(&[1, 1, 0], Some(0.1), Class::Other); + pt.insert_number(&[2, 1, 0], Some(0.01), Class::Other); + pt.insert_number(&[3, 1, 0], Some(0.001), Class::Other); + pt.insert_number(&[4, 1, 0], Some(0.0001), Class::Other); + pt.insert_number(&[5, 1, 0], Some(0.00001), Class::Other); + pt.insert_number(&[6, 1, 0], Some(0.000001), Class::Other); + pt.insert_number(&[7, 1, 0], Some(0.0000001), Class::Other); + pt.insert_number(&[8, 1, 0], Some(0.00000001), Class::Other); + pt.insert_number(&[9, 1, 0], Some(0.000000001), Class::Other); + pt.insert_number(&[0, 1, 1], Some(-1.0), Class::Residual); + pt.insert_number(&[1, 1, 1], Some(-0.1), Class::Residual); + pt.insert_number(&[2, 1, 1], Some(-0.01), Class::Residual); + pt.insert_number(&[3, 1, 1], Some(-0.001), Class::Residual); + pt.insert_number(&[4, 1, 1], Some(-0.0001), Class::Residual); + pt.insert_number(&[5, 1, 1], Some(-0.00001), Class::Residual); + pt.insert_number(&[6, 1, 1], Some(-0.000001), Class::Residual); + pt.insert_number(&[7, 1, 1], Some(-0.0000001), Class::Residual); + pt.insert_number(&[8, 1, 1], Some(-0.00000001), Class::Residual); + pt.insert_number(&[9, 1, 1], Some(-0.000000001), Class::Residual); + let pivot_table = pt.with_look(Arc::new(test_look())).build(); + assert_rendering( + &pivot_table, + "\ +small numbers +╭────────┬─────────────────────────────────────╮ +│ │ result class │ +│ ├───────────────────┬─────────────────┤ +│ │ general │ specific │ +│ ├───────────────────┼─────────────────┤ +│ │ sign │ sign │ +│ ├─────────┬─────────┼────────┬────────┤ +│exponent│ positive│ negative│positive│negative│ +├────────┼─────────┼─────────┼────────┼────────┤ +│0 │ 1.00│ 1.00│ -1.00│ -1.00│ +│-1 │ .10│ .10│ -.10│ -.10│ +│-2 │ .01│ .01│ -.01│ -.01│ +│-3 │ .00│ .00│ .00│ .00│ +│-4 │ .00│ .00│ .00│ .00│ +│-5 │1.00E-005│1.00E-005│ .00│ .00│ +│-6 │1.00E-006│1.00E-006│ .00│ .00│ +│-7 │1.00E-007│1.00E-007│ .00│ .00│ +│-8 │1.00E-008│1.00E-008│ .00│ .00│ +│-9 │1.00E-009│1.00E-009│ .00│ .00│ +╰────────┴─────────┴─────────┴────────┴────────╯ +", + ); +}