-use std::{iter::Peekable, str::FromStr};
+use std::{
+ borrow::Cow,
+ collections::BTreeSet,
+ iter::{Peekable, chain},
+ ops::{Not as _, RangeInclusive},
+ str::FromStr,
+};
+
+use either::Either;
+use itertools::Itertools as _;
use crate::spv::read::graph::Series;
}
}
+#[derive(Clone, Debug)]
+enum DomainComponent {
+ Categorical(Vec<String>),
+ Continuous(Option<RangeInclusive<f64>>),
+ Unity,
+}
+
+/// Cannot blend continuous and categorical domains.
+#[derive(Copy, Clone, Debug, PartialEq, Eq, thiserror::Error, displaydoc::Display)]
+struct BlendError;
+
+impl DomainComponent {
+ fn categories(&self) -> impl Iterator<Item = &str> {
+ match self {
+ DomainComponent::Categorical(items) => Either::Left(items.iter().map(|s| s.as_str())),
+ DomainComponent::Unity => Either::Right(["0.5"].into_iter()),
+ DomainComponent::Continuous(_) => {
+ // Avoid the need for a third return type by using the same type
+ // as `Unity` but dropping the first (only) element.
+ Either::Right([""].into_iter().dropping(1))
+ }
+ }
+ }
+
+ fn into_categories(self) -> Vec<String> {
+ match self {
+ DomainComponent::Categorical(items) => items,
+ DomainComponent::Continuous(_) => Vec::new(),
+ DomainComponent::Unity => vec!["0.5".into()],
+ }
+ }
+
+ fn range(&self) -> Option<RangeInclusive<f64>> {
+ match self {
+ Self::Categorical(items) => items.is_empty().not().then(|| 1.0..=items.len() as f64),
+ Self::Continuous(range) => range.clone(),
+ Self::Unity => Some(0.5..=0.5),
+ }
+ }
+
+ fn split(&self, s: &str) -> Option<Self> {
+ if let Self::Categorical(items) = self {
+ Some(Self::Categorical(
+ match items.into_iter().find(|element| s == *element) {
+ Some(_) => vec![s.into()],
+ None => Vec::new(),
+ },
+ ))
+ } else {
+ None
+ }
+ }
+
+ fn blend(&self, other: &Self) -> Result<Self, BlendError> {
+ fn union(
+ a: &Option<RangeInclusive<f64>>,
+ b: &Option<RangeInclusive<f64>>,
+ ) -> Option<RangeInclusive<f64>> {
+ match (a.clone(), b.clone()) {
+ (Some(a), Some(b)) => {
+ let (a0, a1) = a.into_inner();
+ let (b0, b1) = b.into_inner();
+ Some(a0.min(b0)..=a1.max(b1))
+ }
+ (Some(range), None) | (None, Some(range)) => Some(range),
+ (None, None) => None,
+ }
+ }
+
+ match (self, other) {
+ (Self::Categorical(a), Self::Categorical(b)) => Ok(Self::Categorical(
+ chain(a, b)
+ .collect::<BTreeSet<_>>()
+ .into_iter()
+ .cloned()
+ .collect(),
+ )),
+ (Self::Continuous(a), Self::Continuous(b)) => Ok(Self::Continuous(union(a, b))),
+ (c @ Self::Categorical(_), Self::Unity) | (Self::Unity, c @ Self::Categorical(_)) => {
+ Ok(c.clone())
+ }
+ (Self::Unity, Self::Unity) => Ok(Self::Unity),
+ (Self::Unity, Self::Continuous(range)) | (Self::Continuous(range), Self::Unity) => {
+ Ok(Self::Continuous(union(range, &Some(0.5..=0.5))))
+ }
+ (Self::Categorical(_), Self::Continuous(_))
+ | (Self::Continuous(_), Self::Categorical(_)) => Err(BlendError),
+ }
+ }
+}
+
+struct Categories<'a>(&'a DomainComponent);
+
+impl<'a> Iterator for Categories<'a> {
+ type Item = Cow<'a, str>;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ todo!()
+ }
+}
+
struct VarSet {
data: Vec<Series>,
//domain: Domain,