Skip to main content

agent_uri_dht/
trie.rs

1//! Path trie for efficient prefix matching on capability paths.
2
3use std::collections::HashMap;
4
5use agent_uri::{CapabilityPath, PathSegment};
6
7/// Trie structure for efficient prefix matching on capability paths.
8///
9/// Supports:
10/// - O(d) insert where d is path depth
11/// - O(d) exact lookup
12/// - O(d + n) prefix lookup where n is the number of descendants
13///
14/// # Type Parameter
15///
16/// * `V` - The value type stored at each path node
17///
18/// # Examples
19///
20/// ```
21/// use agent_uri::CapabilityPath;
22/// use agent_uri_dht::PathTrie;
23///
24/// let mut trie: PathTrie<String> = PathTrie::new();
25///
26/// let path1 = CapabilityPath::parse("assistant/chat").unwrap();
27/// let path2 = CapabilityPath::parse("assistant/code").unwrap();
28///
29/// trie.insert(&path1, "Chat Agent".to_string());
30/// trie.insert(&path2, "Code Agent".to_string());
31///
32/// // Exact lookup
33/// let chat_agents = trie.get_exact(&path1);
34/// assert_eq!(chat_agents.len(), 1);
35///
36/// // Prefix lookup for "assistant" finds both
37/// let prefix = CapabilityPath::parse("assistant").unwrap();
38/// let all_assistants = trie.get_prefix(&prefix);
39/// assert_eq!(all_assistants.len(), 2);
40/// ```
41#[derive(Debug, Clone)]
42pub struct PathTrie<V> {
43    /// Children indexed by path segment
44    children: HashMap<String, PathTrie<V>>,
45    /// Values stored at this node
46    values: Vec<V>,
47}
48
49impl<V> Default for PathTrie<V> {
50    fn default() -> Self {
51        Self::new()
52    }
53}
54
55impl<V> PathTrie<V> {
56    /// Creates an empty trie.
57    #[must_use]
58    pub fn new() -> Self {
59        Self {
60            children: HashMap::new(),
61            values: Vec::new(),
62        }
63    }
64
65    /// Returns the number of values stored at this node.
66    #[must_use]
67    pub fn len(&self) -> usize {
68        self.values.len()
69    }
70
71    /// Returns true if this node has no values.
72    #[must_use]
73    pub fn is_empty(&self) -> bool {
74        self.values.is_empty()
75    }
76
77    /// Returns the total number of values in this trie and all descendants.
78    #[must_use]
79    pub fn total_count(&self) -> usize {
80        let mut count = self.values.len();
81        for child in self.children.values() {
82            count += child.total_count();
83        }
84        count
85    }
86
87    /// Returns the number of unique paths (nodes with values) in this trie.
88    #[must_use]
89    pub fn unique_paths(&self) -> usize {
90        let mut count = usize::from(!self.values.is_empty());
91        for child in self.children.values() {
92            count += child.unique_paths();
93        }
94        count
95    }
96
97    /// Returns true if this node has children.
98    #[must_use]
99    pub fn has_children(&self) -> bool {
100        !self.children.is_empty()
101    }
102}
103
104impl<V: Clone> PathTrie<V> {
105    /// Inserts a value at the given path.
106    ///
107    /// Multiple values can be stored at the same path.
108    pub fn insert(&mut self, path: &CapabilityPath, value: V) {
109        let segments = path.segments();
110        self.insert_at_segments(segments, 0, value);
111    }
112
113    fn insert_at_segments(&mut self, segments: &[PathSegment], index: usize, value: V) {
114        if index >= segments.len() {
115            self.values.push(value);
116            return;
117        }
118
119        let segment = segments[index].as_str().to_string();
120        let child = self.children.entry(segment).or_default();
121        child.insert_at_segments(segments, index + 1, value);
122    }
123
124    /// Returns values at the exact path (not including descendants).
125    #[must_use]
126    pub fn get_exact(&self, path: &CapabilityPath) -> Vec<&V> {
127        let segments = path.segments();
128        self.get_exact_at_segments(segments, 0)
129    }
130
131    fn get_exact_at_segments(&self, segments: &[PathSegment], index: usize) -> Vec<&V> {
132        if index >= segments.len() {
133            return self.values.iter().collect();
134        }
135
136        let segment = segments[index].as_str();
137        match self.children.get(segment) {
138            Some(child) => child.get_exact_at_segments(segments, index + 1),
139            None => Vec::new(),
140        }
141    }
142
143    /// Returns values at the path and all descendant paths (prefix match).
144    #[must_use]
145    pub fn get_prefix(&self, path: &CapabilityPath) -> Vec<&V> {
146        let segments = path.segments();
147        self.get_prefix_at_segments(segments, 0)
148    }
149
150    fn get_prefix_at_segments(&self, segments: &[PathSegment], index: usize) -> Vec<&V> {
151        if index >= segments.len() {
152            // We've reached the query path, collect this node and all descendants
153            return self.collect_all();
154        }
155
156        let segment = segments[index].as_str();
157        match self.children.get(segment) {
158            Some(child) => child.get_prefix_at_segments(segments, index + 1),
159            None => Vec::new(),
160        }
161    }
162
163    /// Collects all values at this node and all descendants.
164    fn collect_all(&self) -> Vec<&V> {
165        let mut result: Vec<&V> = self.values.iter().collect();
166        for child in self.children.values() {
167            result.extend(child.collect_all());
168        }
169        result
170    }
171
172    /// Removes values at the path that match the predicate.
173    ///
174    /// Returns the number of values removed.
175    pub fn remove<F>(&mut self, path: &CapabilityPath, predicate: F) -> usize
176    where
177        F: Fn(&V) -> bool,
178    {
179        let segments = path.segments();
180        self.remove_at_segments(segments, 0, &predicate)
181    }
182
183    fn remove_at_segments<F>(
184        &mut self,
185        segments: &[PathSegment],
186        index: usize,
187        predicate: &F,
188    ) -> usize
189    where
190        F: Fn(&V) -> bool,
191    {
192        if index >= segments.len() {
193            let before = self.values.len();
194            self.values.retain(|v| !predicate(v));
195            return before - self.values.len();
196        }
197
198        let segment = segments[index].as_str().to_string();
199        if let Some(child) = self.children.get_mut(&segment) {
200            let removed = child.remove_at_segments(segments, index + 1, predicate);
201            // Clean up empty children
202            if child.is_empty() && !child.has_children() {
203                self.children.remove(&segment);
204            }
205            removed
206        } else {
207            0
208        }
209    }
210
211    /// Clears all values from the trie.
212    pub fn clear(&mut self) {
213        self.values.clear();
214        self.children.clear();
215    }
216}
217
218#[cfg(test)]
219mod tests {
220    use super::*;
221
222    #[test]
223    fn new_trie_is_empty() {
224        let trie: PathTrie<String> = PathTrie::new();
225        assert!(trie.is_empty());
226        assert_eq!(trie.len(), 0);
227        assert_eq!(trie.total_count(), 0);
228        assert_eq!(trie.unique_paths(), 0);
229    }
230
231    #[test]
232    fn insert_and_get_exact() {
233        let mut trie: PathTrie<String> = PathTrie::new();
234        let path = CapabilityPath::parse("assistant/chat").unwrap();
235
236        trie.insert(&path, "test".to_string());
237
238        let results = trie.get_exact(&path);
239        assert_eq!(results.len(), 1);
240        assert_eq!(*results[0], "test");
241    }
242
243    #[test]
244    fn multiple_values_at_same_path() {
245        let mut trie: PathTrie<String> = PathTrie::new();
246        let path = CapabilityPath::parse("assistant/chat").unwrap();
247
248        trie.insert(&path, "agent1".to_string());
249        trie.insert(&path, "agent2".to_string());
250
251        let results = trie.get_exact(&path);
252        assert_eq!(results.len(), 2);
253    }
254
255    #[test]
256    fn get_exact_returns_empty_for_nonexistent_path() {
257        let trie: PathTrie<String> = PathTrie::new();
258        let path = CapabilityPath::parse("assistant/chat").unwrap();
259
260        let results = trie.get_exact(&path);
261        assert!(results.is_empty());
262    }
263
264    #[test]
265    fn get_exact_does_not_include_descendants() {
266        let mut trie: PathTrie<String> = PathTrie::new();
267
268        let path1 = CapabilityPath::parse("assistant").unwrap();
269        let path2 = CapabilityPath::parse("assistant/chat").unwrap();
270
271        trie.insert(&path1, "assistant".to_string());
272        trie.insert(&path2, "chat".to_string());
273
274        let results = trie.get_exact(&path1);
275        assert_eq!(results.len(), 1);
276        assert_eq!(*results[0], "assistant");
277    }
278
279    #[test]
280    fn get_prefix_includes_descendants() {
281        let mut trie: PathTrie<String> = PathTrie::new();
282
283        let path1 = CapabilityPath::parse("assistant").unwrap();
284        let path2 = CapabilityPath::parse("assistant/chat").unwrap();
285        let path3 = CapabilityPath::parse("assistant/code").unwrap();
286
287        trie.insert(&path1, "assistant".to_string());
288        trie.insert(&path2, "chat".to_string());
289        trie.insert(&path3, "code".to_string());
290
291        let results = trie.get_prefix(&path1);
292        assert_eq!(results.len(), 3);
293    }
294
295    #[test]
296    fn get_prefix_at_leaf_returns_only_that_node() {
297        let mut trie: PathTrie<String> = PathTrie::new();
298
299        let path1 = CapabilityPath::parse("assistant").unwrap();
300        let path2 = CapabilityPath::parse("assistant/chat").unwrap();
301
302        trie.insert(&path1, "assistant".to_string());
303        trie.insert(&path2, "chat".to_string());
304
305        let results = trie.get_prefix(&path2);
306        assert_eq!(results.len(), 1);
307        assert_eq!(*results[0], "chat");
308    }
309
310    #[test]
311    fn get_prefix_returns_empty_for_nonexistent_path() {
312        let trie: PathTrie<String> = PathTrie::new();
313        let path = CapabilityPath::parse("nonexistent").unwrap();
314
315        let results = trie.get_prefix(&path);
316        assert!(results.is_empty());
317    }
318
319    #[test]
320    fn remove_matching_values() {
321        let mut trie: PathTrie<String> = PathTrie::new();
322        let path = CapabilityPath::parse("assistant/chat").unwrap();
323
324        trie.insert(&path, "agent1".to_string());
325        trie.insert(&path, "agent2".to_string());
326
327        let removed = trie.remove(&path, |v| v == "agent1");
328        assert_eq!(removed, 1);
329
330        let results = trie.get_exact(&path);
331        assert_eq!(results.len(), 1);
332        assert_eq!(*results[0], "agent2");
333    }
334
335    #[test]
336    fn remove_all_values_cleans_up_node() {
337        let mut trie: PathTrie<String> = PathTrie::new();
338        let path = CapabilityPath::parse("assistant/chat").unwrap();
339
340        trie.insert(&path, "agent1".to_string());
341
342        let removed = trie.remove(&path, |_| true);
343        assert_eq!(removed, 1);
344        assert_eq!(trie.total_count(), 0);
345    }
346
347    #[test]
348    fn remove_from_nonexistent_path_returns_zero() {
349        let mut trie: PathTrie<String> = PathTrie::new();
350        let path = CapabilityPath::parse("nonexistent").unwrap();
351
352        let removed = trie.remove(&path, |_| true);
353        assert_eq!(removed, 0);
354    }
355
356    #[test]
357    fn total_count_includes_all_descendants() {
358        let mut trie: PathTrie<String> = PathTrie::new();
359
360        let path1 = CapabilityPath::parse("assistant").unwrap();
361        let path2 = CapabilityPath::parse("assistant/chat").unwrap();
362        let path3 = CapabilityPath::parse("assistant/code").unwrap();
363        let path4 = CapabilityPath::parse("workflow").unwrap();
364
365        trie.insert(&path1, "a".to_string());
366        trie.insert(&path2, "b".to_string());
367        trie.insert(&path3, "c".to_string());
368        trie.insert(&path4, "d".to_string());
369
370        assert_eq!(trie.total_count(), 4);
371    }
372
373    #[test]
374    fn unique_paths_counts_nodes_with_values() {
375        let mut trie: PathTrie<String> = PathTrie::new();
376
377        let path1 = CapabilityPath::parse("assistant").unwrap();
378        let path2 = CapabilityPath::parse("assistant/chat").unwrap();
379
380        trie.insert(&path1, "a".to_string());
381        trie.insert(&path2, "b".to_string());
382        trie.insert(&path2, "c".to_string()); // Same path, different value
383
384        assert_eq!(trie.unique_paths(), 2);
385    }
386
387    #[test]
388    fn clear_removes_everything() {
389        let mut trie: PathTrie<String> = PathTrie::new();
390
391        let path = CapabilityPath::parse("assistant/chat").unwrap();
392        trie.insert(&path, "test".to_string());
393
394        trie.clear();
395
396        assert!(trie.is_empty());
397        assert_eq!(trie.total_count(), 0);
398        assert!(!trie.has_children());
399    }
400
401    #[test]
402    fn default_creates_empty_trie() {
403        let trie: PathTrie<String> = PathTrie::default();
404        assert!(trie.is_empty());
405    }
406
407    #[test]
408    fn deep_hierarchy() {
409        let mut trie: PathTrie<String> = PathTrie::new();
410
411        let path1 = CapabilityPath::parse("a/b/c/d").unwrap();
412        let path2 = CapabilityPath::parse("a/b/c").unwrap();
413        let path3 = CapabilityPath::parse("a/b").unwrap();
414        let path4 = CapabilityPath::parse("a").unwrap();
415
416        trie.insert(&path1, "deep".to_string());
417        trie.insert(&path2, "mid".to_string());
418        trie.insert(&path3, "shallow".to_string());
419        trie.insert(&path4, "root".to_string());
420
421        // Prefix query at root should find all
422        let results = trie.get_prefix(&path4);
423        assert_eq!(results.len(), 4);
424
425        // Prefix query at mid should find mid and deep
426        let results = trie.get_prefix(&path2);
427        assert_eq!(results.len(), 2);
428    }
429}