From e81a76a9d0b520b56760a165e3ea8a9c70ecf205 Mon Sep 17 00:00:00 2001 From: Ben Pfaff Date: Mon, 26 Oct 2026 20:07:18 -0700 Subject: [PATCH] work on blend operator --- rust/pspp/src/data.rs | 6 ++ rust/pspp/src/spv/read/graph.rs | 34 +++++++++- rust/pspp/src/spv/read/graph/var_set.rs | 85 +++++++++++++++++++++++-- 3 files changed, 117 insertions(+), 8 deletions(-) diff --git a/rust/pspp/src/data.rs b/rust/pspp/src/data.rs index f71fd3c5e8..10f585bda7 100644 --- a/rust/pspp/src/data.rs +++ b/rust/pspp/src/data.rs @@ -430,6 +430,12 @@ pub enum Datum { ), } +impl Default for Datum { + fn default() -> Self { + Self::Number(None) + } +} + impl Datum> { pub fn new_utf8(s: impl Into) -> Self { let s: String = s.into(); diff --git a/rust/pspp/src/spv/read/graph.rs b/rust/pspp/src/spv/read/graph.rs index 82bdcb1f61..46252658a4 100644 --- a/rust/pspp/src/spv/read/graph.rs +++ b/rust/pspp/src/spv/read/graph.rs @@ -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, label: Option, @@ -1253,6 +1253,36 @@ impl Series { self.unique_values = Self::unique_values(&self.values, self.categorical); self } + + fn with_values(&self, values: Vec) -> 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)] diff --git a/rust/pspp/src/spv/read/graph/var_set.rs b/rust/pspp/src/spv/read/graph/var_set.rs index 8d3555a972..507061b45b 100644 --- a/rust/pspp/src/spv/read/graph/var_set.rs +++ b/rust/pspp/src/spv/read/graph/var_set.rs @@ -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, 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, } @@ -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 { + 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); + } } -- 2.47.3