odsek 0.1.0

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

use crate::interval::*;
use crate::intervals::*;

/// An [`Intervals`] stream produced by transforming the endpoint values of an
/// inner stream through a forward / inverse function pair.
///
/// `forward` is applied to endpoints when emitting; `inverse` is applied to
/// query positions before forwarding them to the inner stream's
/// [`head`](Intervals::head). This is the building block underpinning
/// [`IntervalShift`](crate::IntervalShift) and [`IntervalStretch`](crate::IntervalStretch).
pub struct IntervalsEndpointMap<It, F, G, Tin, Tout> {
    it: It,
    forward: F,
    inverse: G,
    _phantom: PhantomData<(Tin, Tout)>,
}

impl<It, F, G, Tin, Tout> IntervalsEndpointMap<It, F, G, Tin, Tout>
where
    F: Fn(Tin) -> Tout,
    G: Fn(Tout) -> Tin,
{
    /// Build an endpoint-mapped stream.
    ///
    /// `forward` and `inverse` must be strictly monotonically increasing
    /// and mutually inverse on the relevant domain. Violating this produces
    /// a nonsensical interval stream.
    pub fn new(it: It, forward: F, inverse: G) -> Self {
        IntervalsEndpointMap {
            it,
            forward,
            inverse,
            _phantom: PhantomData,
        }
    }
}

impl<It, F, G, Tin, Tout> Intervals for IntervalsEndpointMap<It, F, G, Tin, Tout>
where
    It: Intervals,
    It::Endpoint: EndpointMapInto<Tout, EndpointValue = Tin> + Clone,
    <It::Endpoint as EndpointMapInto<Tout>>::Output:
        EndpointMapInto<Tin, EndpointValue = Tout, Output = It::Endpoint>,
    F: Fn(Tin) -> Tout,
    G: Fn(Tout) -> Tin,
    It::Value: Copy,
{
    type Endpoint = <It::Endpoint as EndpointMapInto<Tout>>::Output;
    type Value = It::Value;

    fn head(
        &mut self,
        pos: Option<LeftT<Self::Endpoint>>,
    ) -> Option<Interval<Self::Endpoint, Self::Value>> {
        let p_in: Option<LeftT<It::Endpoint>> =
            pos.map(|e| e.map_endpoint_into(|v| (self.inverse)(v)));
        let r = self.it.head(p_in);
        r.map(|it| {
            let a = it.left().map_endpoint_into(|v| (self.forward)(v));
            let b = it.right().map_endpoint_into(|v| (self.forward)(v));
            let val = it.value();
            Interval::new_lr(a, b, val)
        })
    }
}

/// Adapter trait providing the `.map_endpoints(forward, inverse)` builder
/// method on any [`Intervals`].
///
/// Blanket-implemented for every `Intervals`, so callers get a fluent API
/// for endpoint-value transforms.
pub trait InIntervalsMapEndpoints: Sized {
    /// Transform endpoint values through the `forward` / `inverse` pair.
    ///
    /// The two functions must be strictly monotonically increasing and
    /// mutually inverse — see [`IntervalsEndpointMap::new`].
    fn map_endpoints<F, G, Tin, Tout>(
        self,
        forward: F,
        inverse: G,
    ) -> IntervalsEndpointMap<Self, F, G, Tin, Tout>
    where
        F: Fn(Tin) -> Tout,
        G: Fn(Tout) -> Tin;
}

impl<I> InIntervalsMapEndpoints for I
where
    I: Intervals + Sized,
{
    fn map_endpoints<F, G, Tin, Tout>(
        self,
        forward: F,
        inverse: G,
    ) -> IntervalsEndpointMap<Self, F, G, Tin, Tout>
    where
        F: Fn(Tin) -> Tout,
        G: Fn(Tout) -> Tin,
    {
        IntervalsEndpointMap::new(self, forward, inverse)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::{EndpointOC, EndpointSymmetric, IntervalsSingle};

    #[test]
    fn scale_same_type() {
        let i = Interval::new(EndpointOC::Closed(1), EndpointOC::Open(4), ());
        let is = IntervalsSingle::new(i);
        let mut m = IntervalsEndpointMap::new(is, |x: i32| x * 3, |x: i32| x / 3);

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

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

        let a = m.head(Some(LeftT::Left(EndpointOC::Closed(12))));
        assert_eq!(a, None);
    }

    #[test]
    fn type_change_i32_to_i64() {
        let i = Interval::new(EndpointOC::Closed(1_i32), EndpointOC::Open(4_i32), ());
        let is = IntervalsSingle::new(i);
        let mut m =
            IntervalsEndpointMap::new(is, |x: i32| x as i64 * 1_000, |x: i64| (x / 1_000) as i32);
        let a = m.head(None);
        let expected = Interval::new(
            EndpointOC::Closed(1_000_i64),
            EndpointOC::Open(4_000_i64),
            (),
        );
        assert_eq!(a, Some(expected));
    }

    #[test]
    fn symmetric_endpoint() {
        let i = Interval::new(EndpointSymmetric(2), EndpointSymmetric(5), "A");
        let is = IntervalsSingle::new(i);
        let mut m = IntervalsEndpointMap::new(is, |x: i32| x + 10, |x: i32| x - 10);
        let a = m.head(None);
        let expected = Interval::new(EndpointSymmetric(12), EndpointSymmetric(15), "A");
        assert_eq!(a, Some(expected));
    }

    #[test]
    fn builder_method() {
        let i = Interval::new(EndpointOC::Closed(1), EndpointOC::Open(4), ());
        let mut m = IntervalsSingle::new(i).map_endpoints(|x: i32| x + 5, |x: i32| x - 5);
        let a = m.head(None);
        let expected = Interval::new(EndpointOC::Closed(6), EndpointOC::Open(9), ());
        assert_eq!(a, Some(expected));
    }
}