psyche_utils/bucket_strainer/mod.rs
1//! Tools used to split data collection by their utility/category.
2
3mod bucket;
4mod layer;
5mod rules;
6#[cfg(test)]
7mod tests;
8
9pub use bucket::*;
10pub use layer::*;
11pub use rules::*;
12use std::mem;
13
14/// Bucket strainer is a data collection processor that splits and sorts input data into buckets
15/// with rules that item must obey to fall into them. Items that does not obey any bucket rule, are
16/// leftovers returned by processing.
17///
18/// Best problem that bucket strainer can solve is task commander that will sort AI agents into
19/// buckets that represent different tasks to perform.
20///
21/// # How it works
22/// 1. Bucket strainer contains layers of filtering organized in sequential manner, so there is
23/// more possibility for items to fall into first layers than into last layers.
24/// 1. Each layer contains buckets that will compete for incomming items, item can fall only into
25/// one of all layer buckets and that bucket is selected based on highest score that bucket
26/// will get from item based on bucket rules.
27/// 1. Each Bucket contains collection of items that fall into them and main rule that will score
28/// each incomming item and use that score to tell processor which bucket got highest score and
29/// by that which bucket will get incoming item.
30#[derive(Clone)]
31pub struct BucketStrainer<T>
32where
33 T: Clone,
34{
35 layers: Vec<Layer<T>>,
36}
37
38impl<T> BucketStrainer<T>
39where
40 T: Clone,
41{
42 /// Creates bucket strainer processor.
43 ///
44 /// # Arguments
45 /// * `layers` - List of layers that will process incoming items.
46 ///
47 /// # Return
48 /// Instance of bucket strainer.
49 ///
50 /// # Example
51 /// ```
52 /// use psyche_utils::Scalar;
53 /// use psyche_utils::bucket_strainer::{BucketStrainer, Layer, Bucket, Rule};
54 ///
55 /// #[derive(Clone, Copy)]
56 /// enum EvenOrOddRule {
57 /// Even,
58 /// Odd,
59 /// }
60 ///
61 /// impl Rule<i32> for EvenOrOddRule {
62 /// fn score(&self, item: &i32, _: &Bucket<i32>) -> Scalar {
63 /// let even = match self {
64 /// EvenOrOddRule::Even => 0,
65 /// EvenOrOddRule::Odd => 1,
66 /// };
67 /// if *item % 2 == even {
68 /// 1.0
69 /// } else {
70 /// 0.0
71 /// }
72 /// }
73 ///
74 /// fn box_clone(&self) -> Box<dyn Rule<i32>> {
75 /// Box::new((*self).clone())
76 /// }
77 /// }
78 ///
79 /// let bs = BucketStrainer::new(vec![
80 /// Layer::new(vec![
81 /// Bucket::new("even".to_owned(), Box::new(EvenOrOddRule::Even)),
82 /// ]),
83 /// Layer::new(vec![
84 /// Bucket::new("odd".to_owned(), Box::new(EvenOrOddRule::Odd)),
85 /// ]),
86 /// ]);
87 /// ```
88 pub fn new(layers: Vec<Layer<T>>) -> Self {
89 Self { layers }
90 }
91
92 /// Gets list of layers.
93 ///
94 /// # Return
95 /// Reference to slice of layers.
96 pub fn layers(&self) -> &[Layer<T>] {
97 &self.layers
98 }
99
100 /// Replace existing layers with new ones.
101 ///
102 /// # Arguments
103 /// * `layers` - List of new layers.
104 ///
105 /// # Return
106 /// List of old layers.
107 ///
108 /// # Example
109 /// ```
110 /// use psyche_utils::bucket_strainer::{BucketStrainer, Layer, Bucket, BucketLimitRule};
111 ///
112 /// let mut bs = BucketStrainer::<()>::new(vec![]);
113 /// bs.replace_layers(vec![
114 /// Bucket::new("limit".to_owned(), Box::new(BucketLimitRule::new(3))).into(),
115 /// ]);
116 /// ```
117 pub fn replace_layers(&mut self, layers: Vec<Layer<T>>) -> Vec<Layer<T>> {
118 mem::replace(&mut self.layers, layers)
119 }
120
121 /// Finds bucket by its ID.
122 ///
123 /// # Arguments
124 /// * `id` - Bucket ID.
125 ///
126 /// # Return
127 /// Reference to bucket.
128 ///
129 /// # Example
130 /// ```
131 /// use psyche_utils::bucket_strainer::{BucketStrainer, Layer, Bucket, BucketLimitRule};
132 ///
133 /// let bs = BucketStrainer::<()>::new(vec![
134 /// Bucket::new("limit".to_owned(), Box::new(BucketLimitRule::new(3))).into(),
135 /// ]);
136 /// assert!(bs.bucket("limit").is_some());
137 /// ```
138 pub fn bucket(&self, id: &str) -> Option<&Bucket<T>> {
139 for layer in &self.layers {
140 if let Some(bucket) = layer.bucket(id) {
141 return Some(bucket);
142 }
143 }
144 None
145 }
146
147 /// Clears all layers buckets items collections.
148 ///
149 /// # Example
150 /// ```
151 /// use psyche_utils::bucket_strainer::{BucketStrainer, Layer, Bucket, BucketLimitRule};
152 ///
153 /// let mut bs = BucketStrainer::new(vec![
154 /// Bucket::new("limit".to_owned(), Box::new(BucketLimitRule::new(3))).into(),
155 /// ]);
156 /// bs.process(vec![0, 1, 2, 3, 4, 5, 6]);
157 /// assert_eq!(bs.bucket("limit").unwrap().items().len(), 3);
158 /// bs.clear_layers_buckets();
159 /// assert_eq!(bs.bucket("limit").unwrap().items().len(), 0);
160 /// ```
161 pub fn clear_layers_buckets(&mut self) {
162 for layer in &mut self.layers {
163 layer.clear_buckets();
164 }
165 }
166
167 /// Process input items.
168 ///
169 /// # Arguments
170 /// * `items` - List of items to process.
171 ///
172 /// # Return
173 /// Processed items leftovers that does not fall into any bucket.
174 ///
175 /// # Example
176 /// ```
177 /// use psyche_utils::bucket_strainer::{BucketStrainer, Layer, Bucket, BucketLimitRule};
178 ///
179 /// let mut bs = BucketStrainer::new(vec![
180 /// Bucket::new("limitA".to_owned(), Box::new(BucketLimitRule::new(3))).into(),
181 /// Bucket::new("limitB".to_owned(), Box::new(BucketLimitRule::new(2))).into(),
182 /// ]);
183 /// let leftovers = bs.process(vec![0, 1, 2, 3, 4, 5, 6]);
184 /// assert_eq!(bs.bucket("limitA").unwrap().items(), &[0, 1, 2]);
185 /// assert_eq!(bs.bucket("limitB").unwrap().items(), &[3, 4]);
186 /// assert_eq!(&leftovers, &[5, 6]);
187 /// ```
188 pub fn process(&mut self, mut items: Vec<T>) -> Vec<T> {
189 self.clear_layers_buckets();
190 for layer in &mut self.layers {
191 items = layer.process(items);
192 if items.is_empty() {
193 break;
194 }
195 }
196 items
197 }
198
199 /// Get list of bucket with their items pairs.
200 ///
201 /// # Return
202 /// Pairs of buckets with their items.
203 ///
204 /// # Example
205 /// ```
206 /// use psyche_utils::bucket_strainer::{BucketStrainer, Layer, Bucket, BucketLimitRule};
207 ///
208 /// let mut bs = BucketStrainer::new(vec![
209 /// Bucket::new("limitA".to_owned(), Box::new(BucketLimitRule::new(3))).into(),
210 /// Bucket::new("limitB".to_owned(), Box::new(BucketLimitRule::new(2))).into(),
211 /// ]);
212 /// bs.process(vec![0, 1, 2, 3, 4, 5, 6]);
213 /// let pairs = bs.buckets_items_pairs();
214 /// assert_eq!(pairs.len(), 2);
215 /// assert_eq!(pairs[0].0, "limitA");
216 /// assert_eq!(pairs[0].1.to_vec(), vec![0, 1, 2]);
217 /// assert_eq!(pairs[1].0, "limitB");
218 /// assert_eq!(pairs[1].1.to_vec(), vec![3, 4]);
219 /// ```
220 pub fn buckets_items_pairs<'a>(&'a self) -> Vec<(&'a str, &'a [T])> {
221 self.layers
222 .iter()
223 .flat_map(|layer| {
224 layer
225 .buckets()
226 .iter()
227 .map(|bucket| (bucket.id(), bucket.items()))
228 .collect::<Vec<_>>()
229 })
230 .collect()
231 }
232}