rust_rule_engine/rete/
alpha_memory_index.rs1use super::facts::{FactValue, TypedFacts};
9use std::collections::HashMap;
10
11#[derive(Debug, Clone)]
13pub struct AlphaMemoryIndex {
14 facts: Vec<TypedFacts>,
16
17 indexes: HashMap<String, HashMap<String, Vec<usize>>>,
20
21 stats: IndexStats,
23}
24
25#[derive(Debug, Clone, Default)]
27pub struct IndexStats {
28 query_counts: HashMap<String, usize>,
30
31 total_queries: usize,
33
34 indexed_lookups: usize,
36
37 linear_scans: usize,
39}
40
41impl AlphaMemoryIndex {
42 pub fn new() -> Self {
44 Self {
45 facts: Vec::new(),
46 indexes: HashMap::new(),
47 stats: IndexStats::default(),
48 }
49 }
50
51 pub fn insert(&mut self, fact: TypedFacts) -> usize {
53 let idx = self.facts.len();
54
55 for (field_name, index) in &mut self.indexes {
57 if let Some(value) = fact.get(field_name) {
58 let key = format!("{:?}", value);
59 index.entry(key).or_insert_with(Vec::new).push(idx);
60 }
61 }
62
63 self.facts.push(fact);
64 idx
65 }
66
67 pub fn filter(&self, field: &str, value: &FactValue) -> Vec<&TypedFacts> {
70 if let Some(index) = self.indexes.get(field) {
72 let key = format!("{:?}", value);
73
74 if let Some(indices) = index.get(&key) {
75 return indices.iter().map(|&i| &self.facts[i]).collect();
76 } else {
77 return Vec::new();
78 }
79 }
80
81 self.facts
83 .iter()
84 .filter(|f| f.get(field) == Some(value))
85 .collect()
86 }
87
88 pub fn filter_tracked(&mut self, field: &str, value: &FactValue) -> Vec<&TypedFacts> {
90 self.stats.total_queries += 1;
91 *self
92 .stats
93 .query_counts
94 .entry(field.to_string())
95 .or_insert(0) += 1;
96
97 if let Some(index) = self.indexes.get(field) {
99 let key = format!("{:?}", value);
100 self.stats.indexed_lookups += 1;
101
102 if let Some(indices) = index.get(&key) {
103 return indices.iter().map(|&i| &self.facts[i]).collect();
104 } else {
105 return Vec::new();
106 }
107 }
108
109 self.stats.linear_scans += 1;
111 self.facts
112 .iter()
113 .filter(|f| f.get(field) == Some(value))
114 .collect()
115 }
116
117 pub fn create_index(&mut self, field: String) {
119 if self.indexes.contains_key(&field) {
120 return; }
122
123 let mut index = HashMap::new();
124
125 for (idx, fact) in self.facts.iter().enumerate() {
127 if let Some(value) = fact.get(&field) {
128 let key = format!("{:?}", value);
129 index.entry(key).or_insert_with(Vec::new).push(idx);
130 }
131 }
132
133 self.indexes.insert(field, index);
134 }
135
136 pub fn drop_index(&mut self, field: &str) {
138 self.indexes.remove(field);
139 }
140
141 pub fn get_all(&self) -> &[TypedFacts] {
143 &self.facts
144 }
145
146 pub fn get(&self, idx: usize) -> Option<&TypedFacts> {
148 self.facts.get(idx)
149 }
150
151 pub fn len(&self) -> usize {
153 self.facts.len()
154 }
155
156 pub fn is_empty(&self) -> bool {
158 self.facts.is_empty()
159 }
160
161 pub fn indexed_fields(&self) -> Vec<&String> {
163 self.indexes.keys().collect()
164 }
165
166 pub fn stats(&self) -> &IndexStats {
168 &self.stats
169 }
170
171 pub fn auto_tune(&mut self) {
176 let fields_to_index: Vec<String> = self
177 .stats
178 .query_counts
179 .iter()
180 .filter(|(field, &count)| count > 50 && !self.indexes.contains_key(*field))
181 .map(|(field, _)| field.clone())
182 .collect();
183
184 for field in fields_to_index {
185 self.create_index(field);
186 }
187 }
188
189 pub fn clear(&mut self) {
191 self.facts.clear();
192 self.indexes.clear();
193 self.stats = IndexStats::default();
194 }
195}
196
197impl Default for AlphaMemoryIndex {
198 fn default() -> Self {
199 Self::new()
200 }
201}
202
203impl IndexStats {
204 pub fn speedup_ratio(&self) -> f64 {
206 if self.linear_scans == 0 {
207 return 1.0;
208 }
209
210 let indexed_ratio = self.indexed_lookups as f64 / self.total_queries as f64;
211 let linear_ratio = self.linear_scans as f64 / self.total_queries as f64;
212
213 if linear_ratio > 0.0 {
214 indexed_ratio / linear_ratio
215 } else {
216 1.0
217 }
218 }
219
220 pub fn index_hit_rate(&self) -> f64 {
222 if self.total_queries == 0 {
223 return 0.0;
224 }
225 (self.indexed_lookups as f64 / self.total_queries as f64) * 100.0
226 }
227}
228
229impl std::fmt::Display for IndexStats {
230 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
231 write!(
232 f,
233 "Alpha Index Stats:\n\
234 - Total queries: {}\n\
235 - Indexed lookups: {} ({:.1}%)\n\
236 - Linear scans: {} ({:.1}%)\n\
237 - Most queried fields: {:?}",
238 self.total_queries,
239 self.indexed_lookups,
240 self.index_hit_rate(),
241 self.linear_scans,
242 (100.0 - self.index_hit_rate()),
243 self.query_counts
244 .iter()
245 .take(5)
246 .map(|(k, v)| format!("{}({})", k, v))
247 .collect::<Vec<_>>()
248 )
249 }
250}
251
252#[cfg(test)]
253mod tests {
254 use super::*;
255
256 #[test]
257 fn test_basic_index() {
258 let mut mem = AlphaMemoryIndex::new();
259 mem.create_index("status".to_string());
260
261 for i in 0..100 {
263 let mut fact = TypedFacts::new();
264 fact.set("id", i as i64);
265 fact.set("status", if i % 10 == 0 { "active" } else { "pending" });
266 mem.insert(fact);
267 }
268
269 let active = mem.filter("status", &FactValue::String("active".to_string()));
271 assert_eq!(active.len(), 10); }
273
274 #[test]
275 fn test_filter_without_index() {
276 let mut mem = AlphaMemoryIndex::new();
277
278 for i in 0..50 {
280 let mut fact = TypedFacts::new();
281 fact.set("score", i as i64);
282 mem.insert(fact);
283 }
284
285 let results = mem.filter("score", &FactValue::Integer(25));
287 assert_eq!(results.len(), 1);
288 }
289
290 #[test]
291 fn test_auto_tune() {
292 let mut mem = AlphaMemoryIndex::new();
293
294 for i in 0..100 {
296 let mut fact = TypedFacts::new();
297 fact.set("category", if i % 5 == 0 { "A" } else { "B" });
298 mem.insert(fact);
299 }
300
301 for _ in 0..60 {
303 let _ = mem.filter_tracked("category", &FactValue::String("A".to_string()));
304 }
305
306 assert_eq!(mem.indexed_fields().len(), 0);
308
309 mem.auto_tune();
311 assert_eq!(mem.indexed_fields().len(), 1);
312
313 let _ = mem.filter("category", &FactValue::String("A".to_string()));
315 }
316
317 #[test]
318 fn test_multiple_indexes() {
319 let mut mem = AlphaMemoryIndex::new();
320 mem.create_index("status".to_string());
321 mem.create_index("priority".to_string());
322
323 for i in 0..50 {
324 let mut fact = TypedFacts::new();
325 fact.set("status", if i % 2 == 0 { "active" } else { "inactive" });
326 fact.set("priority", if i % 3 == 0 { "high" } else { "low" });
327 mem.insert(fact);
328 }
329
330 let active = mem.filter("status", &FactValue::String("active".to_string()));
331 assert_eq!(active.len(), 25);
332
333 let high_priority = mem.filter("priority", &FactValue::String("high".to_string()));
334 assert_eq!(high_priority.len(), 17); }
336}