use {
alloc::collections::BinaryHeap,
core::{cmp, iter, mem, num::NonZero},
wyrand::WyRand,
};
pub(crate) struct Partition {
separators: Option<BinaryHeap<cmp::Reverse<usize>>>,
total: usize,
used: usize,
}
#[derive(Default)]
pub(crate) struct Size {
total: usize,
}
impl Size {
#[inline]
pub(crate) fn increasing() -> impl Iterator<Item = Self> {
(0..).map(|total| Self { total })
}
#[inline]
#[expect(
clippy::expect_used,
reason = "Internal invariants: violations should fail loudly."
)]
pub(crate) fn partition(mut self, into_how_many: usize, prng: &mut WyRand) -> Partition {
let Some(branching_factor) = NonZero::new(into_how_many) else {
return Partition::empty();
};
self.total /= branching_factor;
let incremented = self
.total
.checked_add(1)
.expect("PSA from `pbt`: your memory will not hold a term of size `usize::MAX`.");
let modulo = unsafe { NonZero::new_unchecked(incremented) };
let n_separators = unsafe { into_how_many.unchecked_sub(1) };
#[expect(
clippy::as_conversions,
clippy::cast_possible_truncation,
reason = "intentional"
)]
let separators = Some({
iter::repeat_with(|| cmp::Reverse(prng.rand() as usize % modulo))
.take(n_separators)
.collect()
});
Partition {
total: self.total,
used: 0,
separators,
}
}
#[inline]
#[expect(
clippy::as_conversions,
clippy::cast_possible_truncation,
reason = "OK: `u64` is already huge"
)]
#[expect(
clippy::expect_used,
reason = "Internal invariants: violations should fail loudly."
)]
pub(crate) fn should_recurse(&self, prng: &mut WyRand) -> bool {
let incremented =
unsafe {
NonZero::new_unchecked(self.total.checked_add(1).expect(
"PSA from `pbt`: your memory will not hold a term of size `usize::MAX`.",
))
};
(prng.rand() as usize % incremented) != 0
}
#[inline]
#[must_use]
pub(crate) const fn zero() -> Self {
Self { total: 0 }
}
}
impl Partition {
#[inline]
#[must_use]
const fn empty() -> Self {
Self {
separators: None,
total: 0,
used: 0,
}
}
}
impl Iterator for Partition {
type Item = Size;
#[inline]
#[expect(
clippy::expect_used,
clippy::unwrap_in_result,
reason = "Internal invariants: violations should fail loudly."
)]
fn next(&mut self) -> Option<Self::Item> {
let separators = self
.separators
.as_mut()
.expect("INTERNAL ERROR (`pbt`): overdrawn size partition");
let cap = separators.pop().map_or(self.total, |cmp::Reverse(u)| u);
let used = mem::replace(&mut self.used, cap);
Some(Size {
total: unsafe { cap.unchecked_sub(used) },
})
}
}
impl Drop for Partition {
#[inline]
fn drop(&mut self) {
debug_assert_eq!(
self.used, self.total,
"INTERNAL ERROR (`pbt`): unused size partition",
);
}
}
#[cfg(test)]
mod test {
use {super::*, pretty_assertions::assert_eq};
#[test]
fn partition_10() {
let mut prng = WyRand::new(0xBAAD_5EED_BAAD_C0DE);
assert_eq!(
Size { total: 10 }
.partition(3, &mut prng)
.take(3)
.map(|Size { total }| total)
.collect::<Vec<usize>>(),
vec![0, 2, 1],
);
assert_eq!(
Size { total: 10 }
.partition(3, &mut prng)
.take(3)
.map(|Size { total }| total)
.collect::<Vec<usize>>(),
vec![1, 1, 1],
);
assert_eq!(
Size { total: 10 }
.partition(3, &mut prng)
.take(10)
.map(|Size { total }| total)
.collect::<Vec<usize>>(),
vec![0, 2, 1, 0, 0, 0, 0, 0, 0, 0],
);
}
}