partial_function/
lib.rs

1#[macro_use]
2extern crate derive_new;
3
4use std::cmp::Ordering;
5
6/// A regular function that is only defined between lower and higher.
7/// If two functions intersect their higher and lower bounds respectively.
8/// The second will take precedence where f(lower).
9#[derive(new)]
10pub struct DualBoundedFunction<B, O> {
11    /// The stored function f(x) = ???
12    pub func: Box<dyn Fn(B) -> O>,
13    /// The lower bound of the function.
14    pub lower: B,
15    /// The higher bound of the function.
16    pub higher: B,
17}
18
19/// Define a functions defined by multiple functions parts.
20/// See BoundedFunction.
21/// Uses bounds as [lower,higher],
22/// except in the case of a lower bound overlapping a higher bound.
23/// In this case, the lower bound always take precedence.
24pub struct PartialFunction<B, O> {
25    funcs: Vec<DualBoundedFunction<B, O>>,
26}
27
28impl<B: PartialOrd, O> PartialFunction<B, O> {
29    /// Creates a new PartialFunctionBuilder
30    pub fn builder() -> PartialFunctionBuilder<B, O> {
31        PartialFunctionBuilder::new()
32    }
33
34    /// Evaluates the partial function.
35    /// Returns None if no function is defined.
36    pub fn eval(&self, x: B) -> Option<O> {
37        let iter = self.funcs.iter().enumerate();
38        for (i, bounded) in iter {
39            let next = self.funcs.get(i + 1);
40            if (x >= bounded.lower && x < bounded.higher)
41                || (next.is_none() && x == bounded.higher)
42                || (next.is_some() && next.unwrap().lower != bounded.higher)
43            {
44                let f = &bounded.func;
45                return Some(f(x));
46            }
47        }
48        None
49    }
50}
51
52/// A builder to create an immutable PartialFunction.
53#[derive(new, Default)]
54pub struct PartialFunctionBuilder<B, O> {
55    #[new(default)]
56    funcs: Vec<DualBoundedFunction<B, O>>,
57}
58
59impl<B: PartialOrd, O> PartialFunctionBuilder<B, O> {
60    /// Adds a bounded function bounded between [lower,higher[ of function func.
61    pub fn with(mut self, lower: B, higher: B, func: Box<dyn Fn(B) -> O>) -> Self {
62        debug_assert!(self.can_insert(&lower, &higher));
63        let f = DualBoundedFunction {
64            func,
65            lower,
66            higher,
67        };
68        self.funcs.push(f);
69        self
70    }
71
72    /// Check if you can safely insert into the function list for the specified bounds.
73    pub fn can_insert(&self, lower: &B, higher: &B) -> bool {
74        !self.funcs.iter().any(|b| {
75            (lower >= &b.lower && lower < &b.higher)
76                || (higher > &b.lower && higher <= &b.higher)
77                || (lower <= &b.lower && higher >= &b.higher)
78        })
79    }
80
81    /// Builds the PartialFunction from the functions added using with.
82    pub fn build(mut self) -> PartialFunction<B, O> {
83        self.funcs.sort_by(|a, b| {
84            a.lower
85                .partial_cmp(&b.lower)
86                .unwrap_or(a.higher.partial_cmp(&b.higher).unwrap_or(Ordering::Equal))
87        });
88        PartialFunction { funcs: self.funcs }
89    }
90}
91
92/// A lower bounded function is a function that is valid from [x..infinite[, or until it hits another function's start.
93#[derive(new)]
94struct LowerBoundedFunction<B, O> {
95    /// The stored function f(x) = ???
96    pub func: Box<dyn Fn(B) -> O>,
97    /// The lower bound of the function.
98    pub lower: B,
99}
100
101/// A lower partial function is a function that is defined by segments valid from [x..infinite[, or until it hits another function's start.
102/// It starts searching at -infinity and goes up to infinity, and takes the last seen function that contains the desired invariable value (x).
103///
104/// Example:
105/// [0..infinity[ = 5
106/// [1..infinity[ = 10
107///
108/// f(0.5) = 5
109/// f(1) = 10
110/// f(70) = 10
111#[derive(Default)]
112pub struct LowerPartialFunction<B, O>
113where
114    B: PartialOrd,
115{
116    funcs: Vec<LowerBoundedFunction<B, O>>,
117}
118
119impl<B, O> LowerPartialFunction<B, O>
120where
121    B: PartialOrd,
122{
123    /// Creates a new LowerPartialFunctionBuilder.
124    pub fn builder() -> LowerPartialFunctionBuilder<B, O> {
125        LowerPartialFunctionBuilder::new()
126    }
127
128    /// Evaluates the partial function.
129    /// Returns None if no function is defined for the searched invariable value (x).
130    pub fn eval(&self, x: B) -> Option<O> {
131        let iter = self.funcs.iter().enumerate();
132        for (i, bounded) in iter {
133            let next = self.funcs.get(i + 1);
134            if x >= bounded.lower && ((next.is_some() && next.unwrap().lower > x) || next.is_none())
135            {
136                let f = &bounded.func;
137                return Some(f(x));
138            }
139        }
140        None
141    }
142}
143
144/// A builder to create an immutable PartialFunction.
145#[derive(new, Default)]
146pub struct LowerPartialFunctionBuilder<B, O> {
147    #[new(default)]
148    funcs: Vec<LowerBoundedFunction<B, O>>,
149}
150
151impl<B: PartialOrd, O> LowerPartialFunctionBuilder<B, O> {
152    /// Adds a bounded function bounded between [lower,higher[ of function func.
153    pub fn with(mut self, lower: B, func: Box<dyn Fn(B) -> O>) -> Self {
154        debug_assert!(self.can_insert(&lower));
155        let f = LowerBoundedFunction { func, lower };
156        self.funcs.push(f);
157        self
158    }
159
160    /// Check if you can safely insert into the function list for the specified bounds.
161    pub fn can_insert(&self, lower: &B) -> bool {
162        !self.funcs.iter().any(|b| lower == &b.lower)
163    }
164
165    /// Builds the PartialFunction from the functions added using with.
166    pub fn build(mut self) -> LowerPartialFunction<B, O> {
167        self.funcs
168            .sort_by(|a, b| a.lower.partial_cmp(&b.lower).unwrap_or(Ordering::Equal));
169        LowerPartialFunction { funcs: self.funcs }
170    }
171}
172
173/// More convenient syntax to create a partial function
174#[macro_export]
175macro_rules! partfn {
176    ( $( [$start:expr, $end:expr]: $var:ident -> $f:expr,)* ) => {
177        {
178            let mut func = PartialFunction::builder();
179            $( func = func.with($start, $end, Box::new(|$var| $f)); )*
180            func.build()
181        }
182    };
183}
184
185/// More convenient syntax to create a lower partial function
186#[macro_export]
187macro_rules! lowpartfn {
188    ( $( [$bound:expr]: $var:ident -> $f:expr,)* ) => {
189        {
190            let mut func = LowerPartialFunction::builder();
191            $( func = func.with($bound, Box::new(|$var| $f)); )*
192            func.build()
193        }
194    };
195}