use std::fmt::Debug;
use crate::{Limit, prelude::*};
macro_rules! fn_split_integer {
(
$integer:ident,
$split_integer:ident,
$split_integer_n:ident
) => {
#[track_caller]
pub fn $split_integer<const N: usize>(sum: $integer) -> impl Die<[$integer; N]> {
assert_solution_exists(sum, N);
dice::from_fn(move |mut fate| {
let mut parts = [0; N];
if !parts.is_empty() {
split_integer_recur(&mut parts, sum, &mut fate);
}
parts
})
}
#[track_caller]
pub fn $split_integer_n(sum: $integer, n: usize) -> impl Die<Vec<$integer>> {
assert_solution_exists(sum, n);
dice::from_fn(move |mut fate| {
let mut parts = vec![0; n];
if !parts.is_empty() {
split_integer_recur(&mut parts, sum, &mut fate);
}
parts
})
}
};
}
fn_split_integer! { u8, split_u8, split_u8_n }
fn_split_integer! { u16, split_u16, split_u16_n }
fn_split_integer! { u32, split_u32, split_u32_n }
fn_split_integer! { u64, split_u64, split_u64_n }
fn_split_integer! { u128, split_u128, split_u128_n }
fn_split_integer! { usize, split_usize, split_usize_n }
#[track_caller]
pub fn split_limit<const N: usize>(sum: Limit) -> impl Die<[Limit; N]> {
assert_solution_exists(sum, N);
dice::from_fn(move |mut fate| {
let mut parts = [Limit(0); N];
if !parts.is_empty() {
split_integer_recur(&mut parts, sum, &mut fate);
}
parts
})
}
#[track_caller]
pub fn split_limit_n(sum: Limit, n: usize) -> impl Die<Vec<Limit>> {
assert_solution_exists(sum, n);
dice::from_fn(move |mut fate| {
let mut parts = vec![Limit(0); n];
if !parts.is_empty() {
split_integer_recur(&mut parts, sum, &mut fate);
}
parts
})
}
#[track_caller]
fn assert_solution_exists<I: Integer + Debug>(sum: I, n: usize) {
let no_solution_exists = sum != I::ZERO && n == 0;
assert!(
!no_solution_exists,
"Cannot generate {n} parts that sum up to {sum:?}"
);
}
fn split_integer_recur<I: Integer>(parts: &mut [I], sum: I, fate: &mut Fate) {
if sum != I::ZERO {
if parts.len() == 1 {
parts[0] = sum;
} else {
let (left_sum, right_sum) = sum.split(fate);
if parts.len() == 2 {
parts[0] = left_sum;
parts[1] = right_sum;
} else {
let middle_index = if parts.len().is_multiple_of(2) || fate.roll(dice::bool()) {
parts.len() / 2
} else {
parts.len() / 2 + 1
};
split_integer_recur(&mut parts[0..middle_index], left_sum, fate);
split_integer_recur(&mut parts[middle_index..], right_sum, fate);
}
}
}
}
trait Integer: Copy + Eq + Sized {
const ZERO: Self;
fn split(self, fate: &mut Fate) -> (Self, Self);
#[cfg(test)]
fn add(self, rhs: Self) -> Self;
#[cfg(test)]
fn roll(fate: &mut Fate) -> Self;
}
macro_rules! impl_integer {
(
$integer:ident,
$die:ident,
$uni_die:ident
) => {
impl Integer for $integer {
const ZERO: Self = 0;
fn split(self, fate: &mut Fate) -> (Self, Self) {
let left = fate.roll(dice::$uni_die(0..=self));
(left, self - left)
}
#[cfg(test)]
fn add(self, rhs: Self) -> Self {
self + rhs
}
#[cfg(test)]
fn roll(fate: &mut Fate) -> Self {
fate.roll(dice::$die(..))
}
}
};
}
impl_integer! { u8, u8, uni_u8 }
impl_integer! { u16, u16, uni_u16 }
impl_integer! { u32, u32, uni_u32 }
impl_integer! { u64, u64, uni_u64 }
impl_integer! { u128, u128, uni_u128 }
impl_integer! { usize, usize, uni_usize }
impl Integer for Limit {
const ZERO: Self = Limit(0);
fn split(self, fate: &mut Fate) -> (Self, Self) {
let left = fate.roll(dice::uni_u64(0..=self.0));
(Limit(left), Limit(self.0 - left))
}
#[cfg(test)]
fn add(self, rhs: Self) -> Self {
Limit(self.0 + rhs.0)
}
#[cfg(test)]
fn roll(fate: &mut Fate) -> Self {
Limit(fate.roll(dice::u64(..)))
}
}
#[cfg(test)]
mod tests {
use crate::{Limit, prelude::*};
use super::Integer;
fn sum<I: Integer>(parts: &[I]) -> I {
parts.iter().copied().fold(I::ZERO, I::add)
}
macro_rules! split_integer_tests {
(
$integer:ident,
$split_integer:ident,
$split_integer_n:ident:
$split_integer_returns_the_expected_sum_and_count:ident
$split_integer_n_returns_the_expected_sum_and_count:ident
$split_integer_with_zero:ident
) => {
#[test]
fn $split_integer_with_zero() {
Dicetest::repeatedly().run(|mut fate| {
let parts = fate.roll(dice::$split_integer::<0>($integer::ZERO));
assert_eq!(parts, []);
})
}
#[test]
fn $split_integer_returns_the_expected_sum_and_count() {
Dicetest::repeatedly().run(|mut fate| {
let expected_sum = $integer::roll(&mut fate);
let parts = fate.roll(dice::$split_integer::<1>(expected_sum));
let actual_sum = sum(&parts);
assert_eq!(expected_sum, actual_sum);
let parts = fate.roll(dice::$split_integer::<2>(expected_sum));
let actual_sum = sum(&parts);
assert_eq!(expected_sum, actual_sum);
let parts = fate.roll(dice::$split_integer::<3>(expected_sum));
let actual_sum = sum(&parts);
assert_eq!(expected_sum, actual_sum);
let parts = fate.roll(dice::$split_integer::<4>(expected_sum));
let actual_sum = sum(&parts);
assert_eq!(expected_sum, actual_sum);
let parts = fate.roll(dice::$split_integer::<5>(expected_sum));
let actual_sum = sum(&parts);
assert_eq!(expected_sum, actual_sum);
})
}
#[test]
fn $split_integer_n_returns_the_expected_sum_and_count() {
Dicetest::repeatedly().run(|mut fate| {
let expected_count = fate.roll(dice::length(..));
let expected_sum = if expected_count == 0 {
$integer::ZERO
} else {
$integer::roll(&mut fate)
};
let parts = fate.roll(dice::$split_integer_n(expected_sum, expected_count));
let actual_sum = sum(&parts);
let actual_count = parts.len();
assert_eq!(expected_sum, actual_sum);
assert_eq!(expected_count, actual_count);
})
}
};
}
split_integer_tests! { u8, split_u8, split_u8_n:
split_u8_returns_the_expected_sum_and_count
split_u8_n_returns_the_expected_sum_and_count
split_u8_with_zero
}
split_integer_tests! { u16, split_u16, split_u16_n:
split_u16_returns_the_expected_sum_and_count
split_u16_n_returns_the_expected_sum_and_count
split_u16_with_zero
}
split_integer_tests! { u32, split_u32, split_u32_n:
split_u32_returns_the_expected_sum_and_count
split_u32_n_returns_the_expected_sum_and_count
split_u32_with_zero
}
split_integer_tests! { u64, split_u64, split_u64_n:
split_u64_returns_the_expected_sum_and_count
split_u64_n_returns_the_expected_sum_and_count
split_u64_with_zero
}
split_integer_tests! { u128, split_u128, split_u128_n:
split_u128_returns_the_expected_sum_and_count
split_u128_n_returns_the_expected_sum_and_count
split_u128_with_zero
}
split_integer_tests! { usize, split_usize, split_usize_n:
split_usize_returns_the_expected_sum_and_count
split_usize_n_returns_the_expected_sum_and_count
split_usize_with_zero
}
split_integer_tests! { Limit, split_limit, split_limit_n:
split_limit_returns_the_expected_sum_and_count
split_limit_n_returns_the_expected_sum_and_count
split_limit_with_zero
}
#[test]
fn split_u64_n_calc_stats() {
Dicetest::repeatedly()
.passes(0)
.stats_enabled(true)
.run(|mut fate| {
let sum = 8;
let n = 4;
let parts = fate.roll(dice::split_u64_n(sum, n));
stat_debug!(parts);
stat!(
"for all part p: p > 0",
"{}",
parts.iter().all(|&part| part > 0),
);
stat!(
"for all part p: p >= 0.10 * sum / part_count",
"{}",
parts.iter().all(|&part| part * 10 * n as u64 >= sum),
);
stat!(
"for all part p: p >= 0.33 * sum / part_count",
"{}",
parts.iter().all(|&part| part * 3 * n as u64 >= sum),
);
stat!(
"for all part p: p >= 0.50 * sum / part_count",
"{}",
parts.iter().all(|&part| part * 2 * n as u64 >= sum),
);
stat!(
"for all part p: p >= 0.66 * sum / part_count",
"{}",
parts.iter().all(|&part| part * 3 * n as u64 >= sum * 2),
);
stat!(
"for all part p: p >= 0.90 * sum / part_count",
"{}",
parts.iter().all(|&part| part * 10 * n as u64 >= sum * 9),
);
stat!(
"for any part p: p >= 0.10 * sum",
"{}",
parts.iter().any(|&part| part * 10_u64 >= sum),
);
stat!(
"for any part p: p >= 0.33 * sum",
"{}",
parts.iter().any(|&part| part * 3_u64 >= sum),
);
stat!(
"for any part p: p >= 0.50 * sum",
"{}",
parts.iter().any(|&part| part * 2_u64 >= sum),
);
stat!(
"for any part p: p >= 0.66 * sum",
"{}",
parts.iter().any(|&part| part * 3_u64 >= sum * 2),
);
stat!(
"for any part p: p >= 0.90 * sum",
"{}",
parts.iter().any(|&part| part * 10_u64 >= sum * 9),
);
stat!("greatest parts", "{:?}", {
let max = parts.iter().max();
parts
.iter()
.enumerate()
.filter_map(
|(index, part)| {
if Some(part) == max { Some(index) } else { None }
},
)
.collect::<Vec<_>>()
})
})
}
}