]> pintos-os.org Git - pspp/commitdiff
work on blend operator rust
authorBen Pfaff <blp@cs.stanford.edu>
Tue, 27 Oct 2026 03:07:18 +0000 (20:07 -0700)
committerBen Pfaff <blp@cs.stanford.edu>
Tue, 27 Oct 2026 03:07:18 +0000 (20:07 -0700)
rust/pspp/src/data.rs
rust/pspp/src/spv/read/graph.rs
rust/pspp/src/spv/read/graph/var_set.rs

index f71fd3c5e8a7fa5fb68de9eb3be9659557474da4..10f585bda7c9b068d4be6846e189c1e1035e3116 100644 (file)
@@ -430,6 +430,12 @@ pub enum Datum<T> {
     ),
 }
 
+impl<T> Default for Datum<T> {
+    fn default() -> Self {
+        Self::Number(None)
+    }
+}
+
 impl Datum<WithEncoding<ByteString>> {
     pub fn new_utf8(s: impl Into<String>) -> Self {
         let s: String = s.into();
index 82bdcb1f61bb1d2cbf2433536e8b107b95aca840..46252658a4936948f7225d58a6826c10e0f4ce24 100644 (file)
@@ -21,7 +21,7 @@ use std::{
     collections::{BTreeMap, HashMap},
     fmt::Debug,
     io::{Cursor, ErrorKind},
-    iter::{repeat, zip},
+    iter::{chain, repeat, zip},
     mem::take,
     num::NonZeroUsize,
     ops::{Not, Range},
@@ -1146,7 +1146,7 @@ impl FontMetrics {
     }
 }
 
-#[derive(Clone, Debug)]
+#[derive(Clone, Debug, Default)]
 struct SeriesValue {
     value: Datum<String>,
     label: Option<String>,
@@ -1253,6 +1253,36 @@ impl Series {
         self.unique_values = Self::unique_values(&self.values, self.categorical);
         self
     }
+
+    fn with_values(&self, values: Vec<SeriesValue>) -> Self {
+        Self {
+            name: self.name.clone(),
+            _label: self._label.clone(),
+            _short_label: self._short_label.clone(),
+            _weight: self._weight,
+            _key: self._key,
+            unique_values: Self::unique_values(&values, self.categorical),
+            values,
+            categorical: self.categorical,
+            formatter: self.formatter.clone(),
+        }
+    }
+
+    fn append(&self, other: &Self) -> Self {
+        self.with_values(chain(&self.values, &other.values).cloned().collect_vec())
+    }
+
+    fn reordered(mut self, ordering: &[usize]) -> Self {
+        assert_eq!(self.values.len(), ordering.len());
+        debug_assert!(ordering.iter().copied().all_unique());
+        let mut new_values = vec![SeriesValue::default(); self.values.len()];
+        for (&index, value) in zip(ordering, &self.values) {
+            new_values[index] = value.clone();
+        }
+        self.values = new_values;
+        self.unique_values = Self::unique_values(&self.values, self.categorical);
+        self
+    }
 }
 
 #[derive(Deserialize, Debug)]
index 8d3555a9723340ab96148ebe4140fb738bcb86d9..507061b45be196184b298966f50ced8d8b34a452 100644 (file)
@@ -7,7 +7,7 @@ use std::{
 };
 
 use either::Either;
-use itertools::Itertools as _;
+use itertools::{Itertools as _, merge, zip_eq};
 
 use crate::spv::read::graph::{Series, format::Formatter};
 
@@ -399,6 +399,10 @@ impl Domain {
         }
     }
 
+    pub fn blend(self, other: Domain) -> Self {
+        Self::Blend(Box::new([self, other]))
+    }
+
     pub fn is_empty(&self) -> bool {
         self.len() == 0
     }
@@ -437,7 +441,7 @@ impl Domain {
     }
 }
 
-#[derive(Debug)]
+#[derive(Clone, Debug)]
 pub struct VarSet {
     data: Vec<Series>,
     domain: Domain,
@@ -530,8 +534,17 @@ impl VarSet {
         todo!()
     }
 
-    pub fn blend(self, _other: Self) -> Self {
-        todo!()
+    pub fn blend(self, other: Self) -> Self {
+        Self {
+            data: {
+                let ordering = self.case_mapping.blend_ordering(&other.case_mapping);
+                zip_eq(&self.data, &other.data)
+                    .map(|(left, right)| left.append(right).reordered(&ordering))
+                    .collect_vec()
+            },
+            domain: self.domain.blend(other.domain),
+            case_mapping: self.case_mapping.blend(&other.case_mapping),
+        }
     }
 }
 
@@ -555,7 +568,7 @@ struct DimensionInfo {
 
 // `PartialEq` might need to be implemented manually if we introduce a field for
 // caching the reverse mapping.
-#[derive(Debug, PartialEq, Eq)]
+#[derive(Clone, Debug, PartialEq, Eq)]
 struct CaseMapping {
     map: Vec<usize>,
 }
@@ -613,13 +626,43 @@ impl CaseMapping {
         self.map = indexes.into_iter().map(|index| self.map[index]).collect();
         self
     }
+
+    pub fn blend(&self, other: &Self) -> Self {
+        Self {
+            map: merge(self.map.iter().copied(), other.map.iter().copied()).collect(),
+        }
+    }
+
+    pub fn blend_ordering(&self, other: &Self) -> Vec<usize> {
+        let mut ordering = vec![0; self.len() + other.len()];
+        let mut indexes = [0, self.len()];
+        for (index, side) in merge(
+            self.map.iter().map(|&index| (index, 0)),
+            other.map.iter().map(|&index| (index, 1)),
+        )
+        .map(|(_index, side)| side)
+        .enumerate()
+        {
+            ordering[indexes[side]] = index;
+            indexes[side] += 1;
+        }
+        ordering
+    }
 }
 
 #[cfg(test)]
 mod tests {
     use std::str::FromStr;
 
-    use crate::spv::read::graph::var_set::{Expression, ExpressionParseError, Operator};
+    use crate::{
+        data::Datum,
+        locale::data::EN,
+        spv::read::graph::{
+            Series, SeriesValue,
+            format::Formatter,
+            var_set::{Expression, ExpressionParseError, Operator, VarSet},
+        },
+    };
 
     #[test]
     fn parse_expression() {
@@ -758,4 +801,34 @@ mod tests {
             Err(ExpressionParseError::UnexpectedEof)
         );
     }
+
+    #[test]
+    fn algebra() {
+        let lhs = VarSet::for_variable(&Series::new(
+            String::from("lhs"),
+            vec![
+                SeriesValue::from(Datum::String(String::from("x"))),
+                SeriesValue::from(Datum::String(String::from("y"))),
+                SeriesValue::from(Datum::String(String::from("z"))),
+            ],
+            true,
+            Formatter::default_for_locale(&EN),
+        ));
+        dbg!(&lhs);
+        let rhs = VarSet::for_variable(&Series::new(
+            String::from("lhs"),
+            vec![
+                SeriesValue::from(Datum::String(String::from("a"))),
+                SeriesValue::from(Datum::String(String::from("b"))),
+                SeriesValue::from(Datum::String(String::from("c"))),
+            ],
+            true,
+            Formatter::default_for_locale(&EN),
+        ));
+        dbg!(&rhs);
+        let cross = lhs.clone().cross(rhs.clone());
+        dbg!(&cross);
+        let nest = lhs.nest(rhs);
+        dbg!(&nest);
+    }
 }