work
authorBen Pfaff <blp@cs.stanford.edu>
Wed, 28 Aug 2024 15:45:54 +0000 (08:45 -0700)
committerBen Pfaff <blp@cs.stanford.edu>
Wed, 28 Aug 2024 15:45:54 +0000 (08:45 -0700)
rust/pspp/src/output/pivot/mod.rs

index d8f5c9f17f5a3c8eea8912ac261e527fd09b74fa..394d01537e108a66a36609dfa95b1c88305f6ffd 100644 (file)
@@ -142,18 +142,58 @@ pub enum Axis3 {
 
 /// An axis within a pivot table.
 #[derive(Default)]
-pub struct TableAxis {
+pub struct Axis {
     /// `dimensions[0]` is the innermost dimension.
     dimensions: Vec<Dimension>,
 
     /// The number of rows or columns along the axis, that is, the product of
-    /// `dimensions[*].n_leaves`.  It is 0 if any dimension has 0 leaves.
+    /// `dimensions[*].len()`.  It is 0 if any dimension has 0 leaves.
     extent: usize,
 
     /// Sum of `dimensions[*].label_depth`.
     label_depth: usize,
 }
 
+pub struct AxisCursor<'a> {
+    axis: &'a Axis,
+    indexes: Vec<usize>,
+}
+
+impl<'a> AxisCursor<'a> {
+    fn next(&'a mut self) -> Option<&'a Vec<usize>> {
+        if self.indexes.is_empty() {
+            if self
+                .axis
+                .dimensions
+                .iter()
+                .any(|dimension| dimension.is_empty())
+            {
+                return None;
+            }
+            self.indexes = vec![0; self.axis.dimensions.len()];
+            Some(&self.indexes)
+        } else {
+            for (index, dimension) in self.indexes.iter_mut().zip(self.axis.dimensions.iter()) {
+                *index += 1;
+                if *index < dimension.len() {
+                    return Some(&self.indexes);
+                };
+                *index = 0
+            }
+            None
+        }
+    }
+}
+
+impl Axis {
+    fn cursor(&self) -> AxisCursor {
+        AxisCursor {
+            axis: self,
+            indexes: Vec::new(),
+        }
+    }
+}
+
 /// Dimensions.
 ///
 /// A [Dimension] identifies the categories associated with a single dimension
@@ -201,6 +241,16 @@ pub struct Dimension {
     label_depth: usize,
 }
 
+impl Dimension {
+    pub fn is_empty(&self) -> bool {
+        self.len() == 0
+    }
+
+    pub fn len(&self) -> usize {
+        self.data_leaves.len()
+    }
+}
+
 pub struct Group {
     name: Value,
     label_depth: usize,
@@ -590,8 +640,8 @@ pub struct Table {
     caption: Option<Value>,
     notes: Option<String>,
     dimensions: Vec<Dimension>,
-    axes: EnumMap<Axis3, TableAxis>,
-    cells: HashMap<u64, Value>,
+    axes: EnumMap<Axis3, Axis>,
+    cells: HashMap<usize, Value>,
 }
 
 impl Table {
@@ -629,6 +679,24 @@ impl Table {
             cells: HashMap::new(),
         }
     }
+
+    fn cell_index(&self, data_indexes: &[usize]) -> usize {
+        debug_assert_eq!(data_indexes.len(), self.dimensions.len());
+        let mut index = 0;
+        for (dimension, data_index) in self.dimensions.iter().zip(data_indexes.iter()) {
+            debug_assert!(*data_index < dimension.len());
+            index = dimension.len() * index + data_index;
+        }
+        index
+    }
+
+    fn insert(&mut self, data_indexes: &[usize], value: Value) {
+        self.cells.insert(self.cell_index(data_indexes), value);
+    }
+
+    fn get(&self, data_indexes: &[usize]) -> Option<&Value> {
+        self.cells.get(&self.cell_index(data_indexes))
+    }
 }
 
 /// Whether to show variable or value labels or the underlying value or variable name.