use super::BoundRational;
#[derive(Debug, Clone, Default)]
pub(crate) struct Divisors {
members: Vec<BoundRational>,
folded: Vec<BoundRational>,
}
impl PartialEq for Divisors {
fn eq(&self, other: &Self) -> bool {
self.folded == other.folded
}
}
impl Eq for Divisors {}
impl std::hash::Hash for Divisors {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.folded.hash(state);
}
}
impl PartialOrd for Divisors {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Divisors {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.folded.cmp(&other.folded)
}
}
impl Divisors {
pub(crate) fn one(step: BoundRational) -> Self {
Self::from_members(vec![step])
}
fn from_members(mut members: Vec<BoundRational>) -> Self {
members.sort();
members.dedup();
let folded = fold(members.clone());
Self { members, folded }
}
pub(crate) fn is_empty(&self) -> bool {
self.members.is_empty()
}
pub(crate) fn as_slice(&self) -> &[BoundRational] {
&self.folded
}
pub(crate) fn sole(&self) -> Option<&BoundRational> {
match self.folded.as_slice() {
[step] => Some(step),
_ => None,
}
}
pub(crate) fn intersect(mut self, other: Self) -> Self {
self.members.extend(other.members);
Self::from_members(self.members)
}
pub(crate) fn over_integers(mut self) -> Self {
self.members.retain(|step| !step.is_vacuous_over_integers());
Self::from_members(self.members)
}
pub(crate) fn admit_between(
&self,
minimum: Option<&super::BoundNumber>,
maximum: Option<&super::BoundNumber>,
) -> bool {
self.folded
.iter()
.all(|step| step.admits_between(minimum, maximum))
}
pub(crate) fn divide(&self, value: &serde_json::Number) -> bool {
self.members.iter().all(|step| step.divides(value))
}
pub(crate) fn divide_all(&self, other: &Self) -> bool {
self.folded.iter().all(|step| {
other
.folded
.iter()
.any(|finer| step.shares_arithmetic(finer) && step.divides_divisor(finer))
})
}
}
fn fold(mut divisors: Vec<BoundRational>) -> Vec<BoundRational> {
loop {
if let Some((left, right, lcm)) = first_foldable_pair(&divisors) {
divisors[left] = lcm;
divisors.remove(right);
} else if let Some((index, stripped)) = first_strippable(&divisors) {
divisors[index] = stripped;
} else {
break;
}
divisors.sort();
divisors.dedup();
}
if divisors
.iter()
.any(|step| step.admits_only_whole() && !step.is_identity())
{
divisors.retain(|step| !step.is_identity());
}
divisors
}
fn first_strippable(divisors: &[BoundRational]) -> Option<(usize, BoundRational)> {
divisors.iter().enumerate().find_map(|(index, step)| {
divisors
.iter()
.enumerate()
.filter(|(other, _)| *other != index)
.find_map(|(_, other)| step.without_factors_of(other))
.map(|stripped| (index, stripped))
})
}
fn first_foldable_pair(divisors: &[BoundRational]) -> Option<(usize, usize, BoundRational)> {
divisors.iter().enumerate().find_map(|(left, step)| {
divisors
.iter()
.enumerate()
.skip(left + 1)
.find_map(|(right, other)| step.checked_lcm(other).map(|lcm| (left, right, lcm)))
})
}