Skip to main content

solverforge_scoring/stream/
collection_extract.rs

1/* CollectionExtract trait for ergonomic entity collection extraction.
2
3Allows extractor closures to return either `&[A]` or `&Vec<A>`,
4so users can write `vec(|s| &s.employees)` without `.as_slice()`.
5
6# Usage
7
8```
9use solverforge_scoring::stream::collection_extract::{CollectionExtract, VecExtract, vec};
10
11struct Schedule { employees: Vec<String> }
12
13// Direct slice closure — works out of the box:
14let e1 = |s: &Schedule| s.employees.as_slice();
15let _: &[String] = e1.extract(&Schedule { employees: vec![] });
16
17// Vec reference closure — wrap with `vec(...)`:
18let e2 = vec(|s: &Schedule| &s.employees);
19let _: &[String] = e2.extract(&Schedule { employees: vec![] });
20```
21*/
22
23/* Extracts a slice of entities from the solution.
24
25The associated type `Item` names the entity type, allowing callers to
26write `E: CollectionExtract<S, Item = A>` when `A` must be inferred from `E`
27rather than stated as a separate generic parameter.
28*/
29pub trait CollectionExtract<S>: Send + Sync {
30    // The entity type yielded by this extractor.
31    type Item;
32
33    // Extracts the entity slice from the solution.
34    fn extract<'s>(&self, s: &'s S) -> &'s [Self::Item];
35}
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq)]
38pub enum ChangeSource {
39    Static,
40    Descriptor(usize),
41}
42
43pub trait TrackedCollectionExtract<S>: CollectionExtract<S> {
44    fn change_source(&self) -> ChangeSource;
45}
46
47pub trait FlattenExtract<P>: Send + Sync {
48    type Item;
49
50    fn extract<'s>(&self, parent: &'s P) -> &'s [Self::Item];
51}
52
53impl<S, A, F> CollectionExtract<S> for F
54where
55    F: for<'a> Fn(&'a S) -> &'a [A] + Send + Sync,
56{
57    type Item = A;
58
59    #[inline]
60    fn extract<'s>(&self, s: &'s S) -> &'s [A] {
61        self(s)
62    }
63}
64
65impl<P, B, F> FlattenExtract<P> for F
66where
67    F: for<'a> Fn(&'a P) -> &'a [B] + Send + Sync,
68{
69    type Item = B;
70
71    #[inline]
72    fn extract<'s>(&self, parent: &'s P) -> &'s [B] {
73        self(parent)
74    }
75}
76
77#[derive(Clone, Copy)]
78pub struct FlattenVecExtract<F>(pub F);
79
80impl<P, B, F> FlattenExtract<P> for FlattenVecExtract<F>
81where
82    F: for<'a> Fn(&'a P) -> &'a Vec<B> + Send + Sync,
83{
84    type Item = B;
85
86    #[inline]
87    fn extract<'s>(&self, parent: &'s P) -> &'s [B] {
88        (self.0)(parent).as_slice()
89    }
90}
91
92#[derive(Clone, Copy)]
93pub struct TrackedExtract<E> {
94    extractor: E,
95    change_source: ChangeSource,
96}
97
98impl<E> TrackedExtract<E> {
99    pub fn new(extractor: E, change_source: ChangeSource) -> Self {
100        Self {
101            extractor,
102            change_source,
103        }
104    }
105
106    pub fn extractor(&self) -> &E {
107        &self.extractor
108    }
109}
110
111impl<S, E> CollectionExtract<S> for TrackedExtract<E>
112where
113    E: CollectionExtract<S>,
114{
115    type Item = E::Item;
116
117    #[inline]
118    fn extract<'s>(&self, s: &'s S) -> &'s [Self::Item] {
119        self.extractor.extract(s)
120    }
121}
122
123impl<S, E> TrackedCollectionExtract<S> for TrackedExtract<E>
124where
125    E: CollectionExtract<S>,
126{
127    fn change_source(&self) -> ChangeSource {
128        self.change_source
129    }
130}
131
132/* Wraps a `Fn(&S) -> &Vec<A>` closure so it satisfies `CollectionExtract<S>`.
133
134Construct via the [`vec`] free function.
135*/
136pub struct VecExtract<F>(pub F);
137
138impl<S, A, F> CollectionExtract<S> for VecExtract<F>
139where
140    F: for<'a> Fn(&'a S) -> &'a Vec<A> + Send + Sync,
141{
142    type Item = A;
143
144    #[inline]
145    fn extract<'s>(&self, s: &'s S) -> &'s [A] {
146        (self.0)(s).as_slice()
147    }
148}
149
150/* Wraps a `Fn(&S) -> &Vec<A>` closure into a [`VecExtract`] that satisfies
151[`CollectionExtract<S>`].
152
153Use this when your solution field is a `Vec<A>` and you want to write
154`|s| &s.field` instead of `|s| s.field.as_slice()`.
155
156# Example
157
158```
159use solverforge_scoring::stream::collection_extract::{CollectionExtract, vec};
160
161struct Schedule { employees: Vec<String> }
162
163let extractor = vec(|s: &Schedule| &s.employees);
164let schedule = Schedule { employees: vec!["Alice".into()] };
165assert_eq!(extractor.extract(&schedule), &["Alice".to_string()]);
166```
167*/
168pub fn vec<S, A, F>(f: F) -> VecExtract<F>
169where
170    F: for<'a> Fn(&'a S) -> &'a Vec<A> + Send + Sync,
171{
172    VecExtract(f)
173}
174
175pub fn tracked<E>(extractor: E, change_source: ChangeSource) -> TrackedExtract<E> {
176    TrackedExtract::new(extractor, change_source)
177}