use crate::alphabet::{validate_alphabet, validate_stable_id};
use crate::{AggregateRuleError, AlphabetId, SerialAlphabet};
use std::collections::{BTreeMap, BTreeSet};
use std::fmt::{Display, Formatter};
pub type SymbolCount<S> = (S, usize);
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ProjectionId(String);
impl ProjectionId {
pub fn try_new(value: impl Into<String>) -> Result<Self, AggregateRuleError> {
let value = value.into();
validate_stable_id(&value).map_err(|reason| AggregateRuleError::InvalidProjectionId {
value: value.clone(),
reason,
})?;
Ok(Self(value))
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl Display for ProjectionId {
fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
Display::fmt(&self.0, formatter)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ProjectedClassSpec<S> {
pub id: ProjectionId,
pub symbols: Vec<S>,
pub multiplicity: usize,
}
impl<S> ProjectedClassSpec<S> {
pub fn new(id: ProjectionId, symbols: Vec<S>, multiplicity: usize) -> Self {
Self {
id,
symbols,
multiplicity,
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum AggregateRuleKind {
ExhaustiveExactlyOnce,
NoRepeat,
DeclaredMultiplicity,
DeclaredOmissions,
ProjectedAggregate,
FreeOrder,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum AggregateRule {
ExhaustiveExactlyOnce,
NoRepeat,
DeclaredMultiplicity(DeclaredCounts),
DeclaredOmissions(DeclaredCounts),
ProjectedAggregate(ProjectedRule),
FreeOrder,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct DeclaredCounts {
alphabet_id: AlphabetId,
expected: Vec<usize>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ProjectedClassRule {
id: ProjectionId,
members: Vec<usize>,
multiplicity: usize,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ProjectedRule {
alphabet_id: AlphabetId,
cardinality: usize,
classes: Vec<ProjectedClassRule>,
class_by_position: Vec<usize>,
}
impl AggregateRule {
pub const fn exhaustive_exactly_once() -> Self {
Self::ExhaustiveExactlyOnce
}
pub const fn no_repeat() -> Self {
Self::NoRepeat
}
pub const fn free_order() -> Self {
Self::FreeOrder
}
pub fn declared_multiplicity<A, I>(
alphabet: &A,
declarations: I,
) -> Result<Self, AggregateRuleError>
where
A: SerialAlphabet,
I: IntoIterator<Item = (A::Symbol, usize)>,
{
let positions = validate_alphabet(alphabet)?;
let mut expected = vec![None; alphabet.symbols().len()];
for (symbol, multiplicity) in declarations {
let Some(&position) = positions.get(&symbol) else {
return Err(AggregateRuleError::ForeignSymbol {
alphabet_id: alphabet.id().clone(),
});
};
if expected[position].is_some() {
return Err(AggregateRuleError::DuplicateDeclaration { position });
}
if multiplicity == 0 {
return Err(AggregateRuleError::ZeroMultiplicity { position });
}
expected[position] = Some(multiplicity);
}
let expected = expected
.into_iter()
.enumerate()
.map(|(position, count)| {
count.ok_or(AggregateRuleError::MissingDeclaration { position })
})
.collect::<Result<Vec<_>, _>>()?;
checked_total(&expected)?;
Ok(Self::DeclaredMultiplicity(DeclaredCounts {
alphabet_id: alphabet.id().clone(),
expected,
}))
}
pub fn declared_omissions<A, I>(alphabet: &A, omissions: I) -> Result<Self, AggregateRuleError>
where
A: SerialAlphabet,
I: IntoIterator<Item = A::Symbol>,
{
let positions = validate_alphabet(alphabet)?;
let mut expected = vec![1; alphabet.symbols().len()];
let mut omitted = BTreeSet::new();
for symbol in omissions {
let Some(&position) = positions.get(&symbol) else {
return Err(AggregateRuleError::ForeignSymbol {
alphabet_id: alphabet.id().clone(),
});
};
if !omitted.insert(position) {
return Err(AggregateRuleError::DuplicateDeclaration { position });
}
expected[position] = 0;
}
if omitted.is_empty() {
return Err(AggregateRuleError::NoOmissions);
}
if omitted.len() == alphabet.symbols().len() {
return Err(AggregateRuleError::OmitsEverything(alphabet.id().clone()));
}
Ok(Self::DeclaredOmissions(DeclaredCounts {
alphabet_id: alphabet.id().clone(),
expected,
}))
}
pub fn projected_aggregate<A, I>(alphabet: &A, classes: I) -> Result<Self, AggregateRuleError>
where
A: SerialAlphabet,
I: IntoIterator<Item = ProjectedClassSpec<A::Symbol>>,
{
let positions = validate_alphabet(alphabet)?;
let mut class_ids = BTreeSet::new();
let mut class_by_position = vec![None; alphabet.symbols().len()];
let mut compiled = Vec::new();
let mut total = 0usize;
for spec in classes {
if !class_ids.insert(spec.id.clone()) {
return Err(AggregateRuleError::DuplicateProjectionId(spec.id));
}
if spec.symbols.is_empty() {
return Err(AggregateRuleError::EmptyProjectionClass(spec.id));
}
let class_index = compiled.len();
let mut members = Vec::with_capacity(spec.symbols.len());
for symbol in spec.symbols {
let Some(&position) = positions.get(&symbol) else {
return Err(AggregateRuleError::ForeignSymbol {
alphabet_id: alphabet.id().clone(),
});
};
if class_by_position[position].replace(class_index).is_some() {
return Err(AggregateRuleError::DuplicateProjectionMember { position });
}
members.push(position);
}
total = total
.checked_add(spec.multiplicity)
.ok_or(AggregateRuleError::MultiplicityOverflow)?;
compiled.push(ProjectedClassRule {
id: spec.id,
members,
multiplicity: spec.multiplicity,
});
}
for (position, class) in class_by_position.iter().enumerate() {
if class.is_none() {
return Err(AggregateRuleError::MissingProjectionMember { position });
}
}
if total == 0 {
return Err(AggregateRuleError::OmitsEverything(alphabet.id().clone()));
}
Ok(Self::ProjectedAggregate(ProjectedRule {
alphabet_id: alphabet.id().clone(),
cardinality: alphabet.symbols().len(),
classes: compiled,
class_by_position: class_by_position.into_iter().flatten().collect(),
}))
}
pub const fn kind(&self) -> AggregateRuleKind {
match self {
Self::ExhaustiveExactlyOnce => AggregateRuleKind::ExhaustiveExactlyOnce,
Self::NoRepeat => AggregateRuleKind::NoRepeat,
Self::DeclaredMultiplicity(_) => AggregateRuleKind::DeclaredMultiplicity,
Self::DeclaredOmissions(_) => AggregateRuleKind::DeclaredOmissions,
Self::ProjectedAggregate(_) => AggregateRuleKind::ProjectedAggregate,
Self::FreeOrder => AggregateRuleKind::FreeOrder,
}
}
pub fn declared_counts<A>(
&self,
alphabet: &A,
) -> Result<Option<Vec<SymbolCount<A::Symbol>>>, AggregateRuleError>
where
A: SerialAlphabet,
{
let counts = match self {
Self::DeclaredMultiplicity(counts) | Self::DeclaredOmissions(counts) => counts,
_ => return Ok(None),
};
counts.validate_for(alphabet)?;
Ok(Some(
alphabet
.symbols()
.iter()
.cloned()
.zip(counts.expected.iter().copied())
.collect(),
))
}
pub fn projected_classes<A>(
&self,
alphabet: &A,
) -> Result<Option<Vec<ProjectedClassSpec<A::Symbol>>>, AggregateRuleError>
where
A: SerialAlphabet,
{
let Self::ProjectedAggregate(rule) = self else {
return Ok(None);
};
rule.validate_for(alphabet)?;
Ok(Some(
rule.classes
.iter()
.map(|class| {
ProjectedClassSpec::new(
class.id.clone(),
class
.members
.iter()
.map(|&position| alphabet.symbols()[position].clone())
.collect(),
class.multiplicity,
)
})
.collect(),
))
}
pub(crate) fn declared(&self) -> Option<&DeclaredCounts> {
match self {
Self::DeclaredMultiplicity(counts) | Self::DeclaredOmissions(counts) => Some(counts),
_ => None,
}
}
pub(crate) fn projected(&self) -> Option<&ProjectedRule> {
match self {
Self::ProjectedAggregate(rule) => Some(rule),
_ => None,
}
}
}
impl DeclaredCounts {
pub(crate) fn validate_for<A: SerialAlphabet>(
&self,
alphabet: &A,
) -> Result<(), AggregateRuleError> {
validate_binding(&self.alphabet_id, self.expected.len(), alphabet)
}
pub(crate) fn expected(&self) -> &[usize] {
&self.expected
}
}
impl ProjectedRule {
pub(crate) fn validate_for<A: SerialAlphabet>(
&self,
alphabet: &A,
) -> Result<(), AggregateRuleError> {
validate_binding(&self.alphabet_id, self.cardinality, alphabet)
}
pub(crate) fn required_len(&self) -> Result<usize, AggregateRuleError> {
checked_total(
&self
.classes
.iter()
.map(|class| class.multiplicity)
.collect::<Vec<_>>(),
)
}
pub(crate) fn class_by_position(&self) -> &[usize] {
&self.class_by_position
}
pub(crate) fn classes(&self) -> &[ProjectedClassRule] {
&self.classes
}
}
impl ProjectedClassRule {
pub(crate) fn id(&self) -> &ProjectionId {
&self.id
}
pub(crate) fn multiplicity(&self) -> usize {
self.multiplicity
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ProjectedClassEvidence {
pub id: ProjectionId,
pub expected: usize,
pub observed: usize,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct AggregateLedger<S>
where
S: Clone + Eq + Ord + std::fmt::Debug,
{
pub(crate) alphabet_id: AlphabetId,
pub(crate) rule: AggregateRuleKind,
pub(crate) series_len: usize,
pub(crate) observed: BTreeMap<S, usize>,
pub(crate) expected: Option<BTreeMap<S, usize>>,
pub(crate) omitted: Vec<S>,
pub(crate) repeated: Vec<S>,
pub(crate) projected: Vec<ProjectedClassEvidence>,
}
impl<S> AggregateLedger<S>
where
S: Clone + Eq + Ord + std::fmt::Debug,
{
pub fn alphabet_id(&self) -> &AlphabetId {
&self.alphabet_id
}
pub fn rule(&self) -> AggregateRuleKind {
self.rule
}
pub fn series_len(&self) -> usize {
self.series_len
}
pub fn observed_count(&self, symbol: &S) -> Option<usize> {
self.observed.get(symbol).copied()
}
pub fn expected_count(&self, symbol: &S) -> Option<usize> {
self.expected
.as_ref()
.and_then(|counts| counts.get(symbol).copied())
}
pub fn omitted_symbols(&self) -> &[S] {
&self.omitted
}
pub fn repeated_symbols(&self) -> &[S] {
&self.repeated
}
pub fn projected_classes(&self) -> &[ProjectedClassEvidence] {
&self.projected
}
pub fn is_exhaustive_exactly_once(&self) -> bool {
self.omitted.is_empty()
&& self.repeated.is_empty()
&& self.observed.values().all(|count| *count == 1)
}
}
fn validate_binding<A: SerialAlphabet>(
rule_id: &AlphabetId,
cardinality: usize,
alphabet: &A,
) -> Result<(), AggregateRuleError> {
validate_alphabet(alphabet)?;
if rule_id != alphabet.id() {
return Err(AggregateRuleError::AlphabetMismatch {
rule_id: rule_id.clone(),
series_id: alphabet.id().clone(),
});
}
if cardinality != alphabet.symbols().len() {
return Err(AggregateRuleError::CardinalityMismatch {
expected: cardinality,
found: alphabet.symbols().len(),
});
}
Ok(())
}
fn checked_total(counts: &[usize]) -> Result<usize, AggregateRuleError> {
counts.iter().try_fold(0usize, |total, &count| {
total
.checked_add(count)
.ok_or(AggregateRuleError::MultiplicityOverflow)
})
}