use crate::canonical::ir::{
drop_subsumed, BoundInteger, BoundRational, Bounds, Discrete, Divisors, ExcludedDivisors,
IntegerBounds, IntegerLeaf, Round,
};
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct IntegerLeaves {
leaves: Vec<IntegerLeaf>,
canonical: bool,
}
impl Default for IntegerLeaves {
fn default() -> Self {
Self {
leaves: Vec::new(),
canonical: true,
}
}
}
impl IntegerLeaves {
pub(crate) fn insert(&mut self, leaf: IntegerLeaf) {
self.leaves.push(leaf);
self.canonical = false;
}
fn canonicalize(&mut self) {
if self.canonical {
return;
}
let was_empty = self.leaves.is_empty();
self.leaves = merge(std::mem::take(&mut self.leaves));
drop_subsumed(&mut self.leaves, |outer, inner| {
outer.bounds.covers(&inner.bounds)
&& outer.multiple_of.divide_all(&inner.multiple_of)
&& outer
.not_multiple_of
.bars_no_more_than(&inner.not_multiple_of)
});
self.canonical = true;
debug_assert_eq!(
self.leaves.is_empty(),
was_empty,
"merging emptied the leaves"
);
}
pub(crate) fn clear(&mut self) {
self.leaves.clear();
self.canonical = true;
}
pub(crate) fn retain(&mut self, keep: impl FnMut(&IntegerLeaf) -> bool) {
self.canonicalize();
self.leaves.retain(keep);
}
pub(crate) fn is_empty(&self) -> bool {
self.leaves.is_empty()
}
pub(crate) fn as_slice(&mut self) -> &[IntegerLeaf] {
self.canonicalize();
&self.leaves
}
}
impl IntoIterator for IntegerLeaves {
type Item = IntegerLeaf;
type IntoIter = std::vec::IntoIter<IntegerLeaf>;
fn into_iter(mut self) -> Self::IntoIter {
self.canonicalize();
self.leaves.into_iter()
}
}
struct Group {
divisor: Divisors,
barred: ExcludedDivisors,
}
fn merge(mut leaves: Vec<IntegerLeaf>) -> Vec<IntegerLeaf> {
if leaves.len() < 2 {
return leaves;
}
leaves.sort_by(|left, right| {
left.multiple_of
.cmp(&right.multiple_of)
.then_with(|| left.not_multiple_of.cmp(&right.not_multiple_of))
});
let mut merged: Vec<IntegerLeaf> = Vec::with_capacity(leaves.len());
let mut windows: Vec<IntegerBounds> = Vec::new();
let mut group: Option<Group> = None;
for leaf in leaves {
if group.as_ref().is_none_or(|open| {
open.divisor != leaf.multiple_of || open.barred != leaf.not_multiple_of
}) {
flush_group(&mut merged, group.take(), &mut windows);
group = Some(Group {
divisor: leaf.multiple_of,
barred: leaf.not_multiple_of,
});
}
windows.push(leaf.bounds);
}
flush_group(&mut merged, group, &mut windows);
merged
}
fn flush_group(
merged: &mut Vec<IntegerLeaf>,
group: Option<Group>,
windows: &mut Vec<IntegerBounds>,
) {
let Some(Group {
divisor: multiple_of,
barred: not_multiple_of,
}) = group
else {
return;
};
let step = multiple_of.sole().and_then(BoundRational::exact_integer);
let folded = Bounds::merge_all_across_vacant_gaps(std::mem::take(windows), |end, start| {
step.as_ref()
.is_some_and(|step| steps_over(step, end, start))
});
for bounds in folded {
merged.push(IntegerLeaf {
bounds,
multiple_of: multiple_of.clone(),
not_multiple_of: not_multiple_of.clone(),
});
}
}
fn steps_over(step: &BoundInteger, end: &BoundInteger, start: &BoundInteger) -> bool {
let Some(above) = end.clone().checked_increment() else {
return false;
};
step.multiple_beyond(&above, Round::Up)
.is_some_and(|multiple| multiple >= *start)
}