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}