odsek 0.1.0

Lazy, pull-based composition of mathematical interval sets with open/closed endpoints, no_std-compatible.
Documentation
use core::ops::Div;
use core::ops::Mul;

use crate::ivs::endpoint_map::IntervalsEndpointMap;

/// Scale an [`Intervals`](crate::Intervals) stream by a constant multiplicative factor.
///
/// `IntervalStretch::new(intervals, factor)` returns a stream where every
/// endpoint has been multiplied by `factor`; queries are translated back
/// through division. `factor` must be strictly positive so the endpoint
/// transform remains monotonically increasing and invertible.
pub struct IntervalStretch;

impl IntervalStretch {
    /// Build a scaled stream.
    ///
    /// `Ev` is the underlying endpoint value type and must support `*` and `/`.
    /// `factor` must be greater than `Ev::default()`, which should represent
    /// zero for numeric endpoint values.
    #[allow(clippy::new_ret_no_self)]
    pub fn new<Is, Ev>(
        intervals: Is,
        factor: Ev,
    ) -> IntervalsEndpointMap<Is, impl Fn(Ev) -> Ev + Clone, impl Fn(Ev) -> Ev + Clone, Ev, Ev>
    where
        Ev: Mul<Output = Ev> + Div<Output = Ev> + Clone + Default + PartialOrd,
    {
        assert!(factor > Ev::default(), "factor must be positive");

        let f_fwd = factor.clone();
        let f_inv = factor;
        IntervalsEndpointMap::new(
            intervals,
            move |v: Ev| v * f_fwd.clone(),
            move |v: Ev| v / f_inv.clone(),
        )
    }
}

#[cfg(test)]
mod tests {
    use crate::EndpointOC;
    use crate::Interval;
    use crate::IntervalStretch;
    use crate::Intervals;
    use crate::IntervalsSingle;

    #[test]
    fn stretch_test() {
        let i = Interval::new(EndpointOC::Closed(1), EndpointOC::Open(4), ());
        let is = IntervalsSingle::new(i);
        let mut ism = IntervalStretch::new(is, 3);

        let a = ism.head(None);
        let expected = Interval::new(EndpointOC::Closed(3), EndpointOC::Open(12), ());
        assert!(a == Some(expected));

        let a = ism.head(Some(crate::LeftT::Left(EndpointOC::Closed(6))));
        let expected = Interval::new(EndpointOC::Closed(6), EndpointOC::Open(12), ());
        assert!(a == Some(expected));

        let a = ism.head(Some(crate::LeftT::Left(EndpointOC::Closed(12))));
        let expected = None;
        assert!(a == expected);
    }

    #[test]
    #[should_panic(expected = "factor must be positive")]
    fn zero_factor_panics() {
        let i = Interval::new(EndpointOC::Closed(1), EndpointOC::Open(4), ());
        let is = IntervalsSingle::new(i);

        let _ = IntervalStretch::new(is, 0);
    }

    #[test]
    #[should_panic(expected = "factor must be positive")]
    fn negative_factor_panics() {
        let i = Interval::new(EndpointOC::Closed(1), EndpointOC::Open(4), ());
        let is = IntervalsSingle::new(i);

        let _ = IntervalStretch::new(is, -1);
    }
}