use crate::dice::LengthRange;
use crate::prelude::*;
pub trait CollectionBuilder<T, C> {
fn build(&self, elems: impl ExactSizeIterator<Item = T>) -> C;
}
pub fn collection<T, C, B>(
builder: B,
elem_die: impl Die<T>,
length_range: impl LengthRange,
) -> impl Die<C>
where
B: CollectionBuilder<T, C>,
{
let length_die = dice::length(length_range);
dice::from_fn(move |mut fate| {
let length = fate.roll(&length_die);
let elems = (0..length).map(|_| fate.roll(&elem_die));
builder.build(elems)
})
}
pub fn outer_collection<T, C, B>(
builder: B,
elem_die: impl Die<T>,
length_range: impl LengthRange,
) -> impl Die<C>
where
B: CollectionBuilder<T, C>,
{
let length_die = dice::length(length_range);
dice::from_fn(move |mut fate| {
let length = fate.roll(&length_die);
let elem_limits = if length == 0 {
Vec::new()
} else {
fate.roll(dice::split_limit_n(fate.limit(), length))
};
let elems = elem_limits
.into_iter()
.map(|limit| fate.with_limit(limit).roll(&elem_die));
builder.build(elems)
})
}
#[cfg(test)]
mod tests {
use crate::Limit;
use crate::prelude::*;
#[test]
fn outer_collection_overall_length_is_bounded_by_limit() {
Dicetest::repeatedly().run(|mut fate| {
let length = fate.roll(dice::length(..));
let limit = Limit::saturating_from_usize(length);
pub struct TestBuilder;
impl<T> dice::CollectionBuilder<T, Vec<T>> for TestBuilder {
fn build(&self, elems: impl ExactSizeIterator<Item = T>) -> Vec<T> {
elems.collect()
}
}
let elem_die = dice::u8(..);
let vec_die = dice::collection(TestBuilder, elem_die, ..);
let vec_of_vecs_die = dice::outer_collection(TestBuilder, vec_die, ..);
let vec_of_vecs = fate.with_limit(limit).roll(vec_of_vecs_die);
let overall_length = vec_of_vecs.iter().flatten().count();
assert!(overall_length <= length);
})
}
}