Skip to main content

scirs2_core/concurrent/
compressed_trie.rs

1//! Compressed trie (Patricia / radix tree) for string keys.
2//!
3//! Path compression collapses single-child chains into one node whose key
4//! fragment spans multiple characters, yielding both memory savings and
5//! faster traversal.  Shared prefixes are stored only once.
6//!
7//! # Operations
8//!
9//! | Operation          | Complexity       |
10//! |--------------------|-----------------|
11//! | `insert`           | O(k)            |
12//! | `get`              | O(k)            |
13//! | `remove`           | O(k)            |
14//! | `prefix_search`    | O(k + m)        |
15//! | `longest_prefix`   | O(k)            |
16//!
17//! where *k* is the key length and *m* is the number of results.
18
19use std::collections::HashMap;
20
21// ---------------------------------------------------------------------------
22// Internal node
23// ---------------------------------------------------------------------------
24
25/// A node in the compressed trie.
26///
27/// Each node stores a *fragment* of the key (possibly multiple characters)
28/// and an optional value.  Children are indexed by the first byte of the
29/// remaining suffix.
30struct TrieNode<V> {
31    /// The fragment of the key stored at this node.
32    fragment: String,
33    /// Value stored at this node (present only if this node represents a
34    /// complete key insertion).
35    value: Option<V>,
36    /// Children keyed by the first byte of their fragment.
37    children: HashMap<u8, Box<TrieNode<V>>>,
38}
39
40impl<V> TrieNode<V> {
41    fn new(fragment: String, value: Option<V>) -> Self {
42        TrieNode {
43            fragment,
44            value,
45            children: HashMap::new(),
46        }
47    }
48
49    fn is_leaf(&self) -> bool {
50        self.children.is_empty()
51    }
52}
53
54// ---------------------------------------------------------------------------
55// CompressedTrie
56// ---------------------------------------------------------------------------
57
58/// A compressed trie (radix / Patricia tree) for string keys.
59///
60/// Supports insertion, lookup, removal, prefix search, and longest-prefix
61/// matching.
62///
63/// # Example
64///
65/// ```rust
66/// use scirs2_core::concurrent::CompressedTrie;
67///
68/// let mut trie = CompressedTrie::new();
69/// trie.insert("hello", 1);
70/// trie.insert("help", 2);
71/// trie.insert("world", 3);
72///
73/// assert_eq!(trie.get("hello"), Some(&1));
74/// assert_eq!(trie.get("help"), Some(&2));
75/// assert_eq!(trie.len(), 3);
76///
77/// let results = trie.prefix_search("hel");
78/// assert_eq!(results.len(), 2);
79/// ```
80pub struct CompressedTrie<V> {
81    root: TrieNode<V>,
82    len: usize,
83}
84
85impl<V> CompressedTrie<V> {
86    /// Create an empty compressed trie.
87    pub fn new() -> Self {
88        CompressedTrie {
89            root: TrieNode::new(String::new(), None),
90            len: 0,
91        }
92    }
93
94    /// Return the number of entries in the trie.
95    pub fn len(&self) -> usize {
96        self.len
97    }
98
99    /// Return `true` if the trie contains no entries.
100    pub fn is_empty(&self) -> bool {
101        self.len == 0
102    }
103
104    /// Insert a key-value pair.
105    ///
106    /// If the key already exists, the value is replaced and the old value
107    /// is returned.
108    pub fn insert(&mut self, key: &str, value: V) -> Option<V> {
109        let old = Self::insert_recursive(&mut self.root, key, value);
110        if old.is_none() {
111            self.len += 1;
112        }
113        old
114    }
115
116    fn insert_recursive(node: &mut TrieNode<V>, key: &str, value: V) -> Option<V> {
117        if key.is_empty() {
118            // This node is the target.
119            return node.value.replace(value);
120        }
121
122        let first_byte = key.as_bytes()[0];
123
124        if let Some(child) = node.children.get_mut(&first_byte) {
125            let common = common_prefix_len(&child.fragment, key);
126
127            if common == child.fragment.len() {
128                // The child fragment is a full prefix of the remaining key.
129                return Self::insert_recursive(child, &key[common..], value);
130            }
131
132            // Need to split the child at the common prefix point.
133            let child_remaining = child.fragment[common..].to_string();
134            let key_remaining = &key[common..];
135
136            // Create a new internal node for the common prefix.
137            let mut split_node = TrieNode::new(child.fragment[..common].to_string(), None);
138
139            // Move the existing child under the split node with its remaining fragment.
140            let mut old_child = node.children.remove(&first_byte).expect("child must exist");
141            old_child.fragment = child_remaining.clone();
142            let old_first_byte = if child_remaining.is_empty() {
143                // Edge case: common == child.fragment.len() handled above,
144                // so child_remaining is non-empty here. This branch is
145                // unreachable, but we handle it safely.
146                0u8
147            } else {
148                child_remaining.as_bytes()[0]
149            };
150            split_node.children.insert(old_first_byte, old_child);
151
152            if key_remaining.is_empty() {
153                split_node.value = Some(value);
154            } else {
155                let new_child = TrieNode::new(key_remaining.to_string(), Some(value));
156                split_node
157                    .children
158                    .insert(key_remaining.as_bytes()[0], Box::new(new_child));
159            }
160
161            node.children.insert(first_byte, Box::new(split_node));
162            None
163        } else {
164            // No child starts with this byte — create a leaf.
165            let new_node = TrieNode::new(key.to_string(), Some(value));
166            node.children.insert(first_byte, Box::new(new_node));
167            None
168        }
169    }
170
171    /// Look up the value associated with `key`.
172    pub fn get(&self, key: &str) -> Option<&V> {
173        Self::get_recursive(&self.root, key)
174    }
175
176    fn get_recursive<'a>(node: &'a TrieNode<V>, key: &str) -> Option<&'a V> {
177        if key.is_empty() {
178            return node.value.as_ref();
179        }
180
181        let first_byte = key.as_bytes()[0];
182        let child = node.children.get(&first_byte)?;
183        let common = common_prefix_len(&child.fragment, key);
184
185        if common < child.fragment.len() {
186            // The key diverges before the end of this child's fragment.
187            return None;
188        }
189
190        Self::get_recursive(child, &key[common..])
191    }
192
193    /// Remove the entry with the given key, returning its value.
194    pub fn remove(&mut self, key: &str) -> Option<V> {
195        let removed = Self::remove_recursive(&mut self.root, key);
196        if removed.is_some() {
197            self.len -= 1;
198        }
199        removed
200    }
201
202    fn remove_recursive(node: &mut TrieNode<V>, key: &str) -> Option<V> {
203        if key.is_empty() {
204            return node.value.take();
205        }
206
207        let first_byte = key.as_bytes()[0];
208
209        // We need to check the child, potentially remove from it, and then
210        // potentially merge the child if it has only one child remaining.
211        let child = node.children.get_mut(&first_byte)?;
212        let common = common_prefix_len(&child.fragment, key);
213        if common < child.fragment.len() {
214            return None;
215        }
216
217        let removed = Self::remove_recursive(child, &key[common..]);
218        removed.as_ref()?;
219
220        // After removal, check if the child should be cleaned up.
221        let child = match node.children.get(&first_byte) {
222            Some(c) => c,
223            None => return removed,
224        };
225
226        if child.value.is_none() && child.children.is_empty() {
227            // Child is now empty — remove it entirely.
228            node.children.remove(&first_byte);
229        } else if child.value.is_none() && child.children.len() == 1 {
230            // Child has no value and exactly one grandchild — merge them.
231            let mut child = node.children.remove(&first_byte).expect("child exists");
232            let (_, grandchild) = child.children.drain().next().expect("one grandchild");
233            let merged_fragment = format!("{}{}", child.fragment, grandchild.fragment);
234            let mut merged = grandchild;
235            merged.fragment = merged_fragment;
236            let new_first = if merged.fragment.is_empty() {
237                first_byte
238            } else {
239                merged.fragment.as_bytes()[0]
240            };
241            node.children.insert(new_first, merged);
242        }
243
244        removed
245    }
246
247    /// Return all entries whose keys start with the given prefix.
248    ///
249    /// Results are returned as `(key, &value)` pairs in arbitrary order.
250    pub fn prefix_search(&self, prefix: &str) -> Vec<(String, &V)> {
251        let mut results = Vec::new();
252        self.prefix_search_inner(&self.root, prefix, String::new(), &mut results);
253        results
254    }
255
256    fn prefix_search_inner<'a>(
257        &'a self,
258        node: &'a TrieNode<V>,
259        remaining_prefix: &str,
260        accumulated_key: String,
261        results: &mut Vec<(String, &'a V)>,
262    ) {
263        if remaining_prefix.is_empty() {
264            // We've consumed the entire prefix — collect all entries below.
265            self.collect_all(node, &accumulated_key, results);
266            return;
267        }
268
269        let first_byte = remaining_prefix.as_bytes()[0];
270        let child = match node.children.get(&first_byte) {
271            Some(c) => c,
272            None => return,
273        };
274
275        let common = common_prefix_len(&child.fragment, remaining_prefix);
276
277        if common < child.fragment.len() && common < remaining_prefix.len() {
278            // The prefix diverges before the fragment ends — no match.
279            return;
280        }
281
282        if common >= remaining_prefix.len() {
283            // The prefix is consumed within or at the end of this fragment.
284            // Collect all entries below this child.
285            let new_key = format!("{}{}", accumulated_key, child.fragment);
286            self.collect_all(child, &new_key, results);
287        } else {
288            // common == child.fragment.len() and there's more prefix to match.
289            let new_key = format!("{}{}", accumulated_key, child.fragment);
290            self.prefix_search_inner(child, &remaining_prefix[common..], new_key, results);
291        }
292    }
293
294    fn collect_all<'a>(
295        &'a self,
296        node: &'a TrieNode<V>,
297        current_key: &str,
298        results: &mut Vec<(String, &'a V)>,
299    ) {
300        if let Some(ref v) = node.value {
301            results.push((current_key.to_string(), v));
302        }
303        for child in node.children.values() {
304            let child_key = format!("{}{}", current_key, child.fragment);
305            self.collect_all(child, &child_key, results);
306        }
307    }
308
309    /// Find the longest prefix of `key` that exists in the trie.
310    ///
311    /// Returns `(matched_prefix, &value)` or `None` if no prefix matches.
312    pub fn longest_prefix(&self, key: &str) -> Option<(String, &V)> {
313        let mut best: Option<(String, &V)> = None;
314        self.longest_prefix_inner(&self.root, key, String::new(), &mut best);
315        best
316    }
317
318    fn longest_prefix_inner<'a>(
319        &'a self,
320        node: &'a TrieNode<V>,
321        remaining: &str,
322        accumulated: String,
323        best: &mut Option<(String, &'a V)>,
324    ) {
325        // If this node has a value, it's a candidate for longest prefix.
326        if let Some(ref v) = node.value {
327            *best = Some((accumulated.clone(), v));
328        }
329
330        if remaining.is_empty() {
331            return;
332        }
333
334        let first_byte = remaining.as_bytes()[0];
335        let child = match node.children.get(&first_byte) {
336            Some(c) => c,
337            None => return,
338        };
339
340        let common = common_prefix_len(&child.fragment, remaining);
341        if common < child.fragment.len() {
342            // The key diverges before the end of this fragment —
343            // cannot descend further.
344            return;
345        }
346
347        let new_acc = format!("{}{}", accumulated, child.fragment);
348        self.longest_prefix_inner(child, &remaining[common..], new_acc, best);
349    }
350
351    /// Collect all keys in the trie.
352    pub fn keys(&self) -> Vec<String> {
353        let mut result = Vec::with_capacity(self.len);
354        self.collect_keys(&self.root, "", &mut result);
355        result
356    }
357
358    fn collect_keys(&self, node: &TrieNode<V>, prefix: &str, result: &mut Vec<String>) {
359        if node.value.is_some() {
360            result.push(prefix.to_string());
361        }
362        for child in node.children.values() {
363            let child_key = format!("{}{}", prefix, child.fragment);
364            self.collect_keys(child, &child_key, result);
365        }
366    }
367
368    /// Check whether the trie contains the given key.
369    pub fn contains(&self, key: &str) -> bool {
370        self.get(key).is_some()
371    }
372}
373
374impl<V> Default for CompressedTrie<V> {
375    fn default() -> Self {
376        Self::new()
377    }
378}
379
380// ---------------------------------------------------------------------------
381// Utility
382// ---------------------------------------------------------------------------
383
384/// Return the length (in bytes) of the longest common prefix of `a` and `b`.
385fn common_prefix_len(a: &str, b: &str) -> usize {
386    a.bytes().zip(b.bytes()).take_while(|(x, y)| x == y).count()
387}
388
389// ---------------------------------------------------------------------------
390// Tests
391// ---------------------------------------------------------------------------
392
393#[cfg(test)]
394mod tests {
395    use super::*;
396
397    #[test]
398    fn test_insert_and_get() {
399        let mut trie = CompressedTrie::new();
400        trie.insert("hello", 1);
401        trie.insert("help", 2);
402        trie.insert("world", 3);
403
404        assert_eq!(trie.get("hello"), Some(&1));
405        assert_eq!(trie.get("help"), Some(&2));
406        assert_eq!(trie.get("world"), Some(&3));
407        assert_eq!(trie.get("hel"), None);
408        assert_eq!(trie.get("xyz"), None);
409        assert_eq!(trie.len(), 3);
410    }
411
412    #[test]
413    fn test_prefix_search() {
414        let mut trie = CompressedTrie::new();
415        trie.insert("apple", 1);
416        trie.insert("application", 2);
417        trie.insert("apply", 3);
418        trie.insert("banana", 4);
419
420        let results = trie.prefix_search("app");
421        assert_eq!(results.len(), 3);
422        let mut keys: Vec<String> = results.iter().map(|(k, _)| k.clone()).collect();
423        keys.sort();
424        assert_eq!(keys, vec!["apple", "application", "apply"]);
425
426        let results2 = trie.prefix_search("ban");
427        assert_eq!(results2.len(), 1);
428        assert_eq!(results2[0].0, "banana");
429
430        let results3 = trie.prefix_search("xyz");
431        assert!(results3.is_empty());
432    }
433
434    #[test]
435    fn test_longest_prefix() {
436        let mut trie = CompressedTrie::new();
437        trie.insert("/", "root");
438        trie.insert("/api", "api");
439        trie.insert("/api/v1", "api_v1");
440        trie.insert("/api/v1/users", "users");
441
442        let result = trie.longest_prefix("/api/v1/users/123");
443        assert!(result.is_some());
444        let (prefix, val) = result.expect("should find a match");
445        assert_eq!(prefix, "/api/v1/users");
446        assert_eq!(*val, "users");
447
448        let result2 = trie.longest_prefix("/api/v2/data");
449        assert!(result2.is_some());
450        let (prefix2, val2) = result2.expect("should find /api");
451        assert_eq!(prefix2, "/api");
452        assert_eq!(*val2, "api");
453
454        let result3 = trie.longest_prefix("/other");
455        assert!(result3.is_some());
456        let (prefix3, _) = result3.expect("should find /");
457        assert_eq!(prefix3, "/");
458
459        // No match at all
460        let mut trie2: CompressedTrie<i32> = CompressedTrie::new();
461        trie2.insert("abc", 1);
462        assert!(trie2.longest_prefix("xyz").is_none());
463    }
464
465    #[test]
466    fn test_remove() {
467        let mut trie = CompressedTrie::new();
468        trie.insert("hello", 1);
469        trie.insert("help", 2);
470        trie.insert("world", 3);
471
472        assert_eq!(trie.remove("hello"), Some(1));
473        assert_eq!(trie.get("hello"), None);
474        assert_eq!(trie.len(), 2);
475        // "help" should still work
476        assert_eq!(trie.get("help"), Some(&2));
477
478        assert_eq!(trie.remove("nonexistent"), None);
479        assert_eq!(trie.len(), 2);
480    }
481
482    #[test]
483    fn test_path_compression() {
484        let mut trie = CompressedTrie::new();
485        // Insert a long key — the fragment should be the full string.
486        trie.insert("abcdefghij", 1);
487        assert_eq!(trie.get("abcdefghij"), Some(&1));
488        assert_eq!(trie.len(), 1);
489
490        // Insert a key that shares a prefix — should split.
491        trie.insert("abcde12345", 2);
492        assert_eq!(trie.get("abcdefghij"), Some(&1));
493        assert_eq!(trie.get("abcde12345"), Some(&2));
494        assert_eq!(trie.len(), 2);
495    }
496
497    #[test]
498    fn test_empty_trie() {
499        let trie: CompressedTrie<i32> = CompressedTrie::new();
500        assert!(trie.is_empty());
501        assert_eq!(trie.len(), 0);
502        assert_eq!(trie.get("anything"), None);
503        assert!(trie.keys().is_empty());
504        assert!(trie.prefix_search("").is_empty());
505        assert!(trie.longest_prefix("test").is_none());
506    }
507
508    #[test]
509    fn test_keys() {
510        let mut trie = CompressedTrie::new();
511        trie.insert("cat", 1);
512        trie.insert("car", 2);
513        trie.insert("card", 3);
514        trie.insert("dog", 4);
515
516        let mut keys = trie.keys();
517        keys.sort();
518        assert_eq!(keys, vec!["car", "card", "cat", "dog"]);
519    }
520
521    #[test]
522    fn test_overwrite_value() {
523        let mut trie = CompressedTrie::new();
524        let old = trie.insert("key", 1);
525        assert!(old.is_none());
526
527        let old2 = trie.insert("key", 2);
528        assert_eq!(old2, Some(1));
529        assert_eq!(trie.get("key"), Some(&2));
530        assert_eq!(trie.len(), 1); // length unchanged
531    }
532
533    #[test]
534    fn test_single_char_keys() {
535        let mut trie = CompressedTrie::new();
536        trie.insert("a", 1);
537        trie.insert("b", 2);
538        trie.insert("c", 3);
539
540        assert_eq!(trie.get("a"), Some(&1));
541        assert_eq!(trie.get("b"), Some(&2));
542        assert_eq!(trie.get("c"), Some(&3));
543        assert_eq!(trie.len(), 3);
544    }
545
546    #[test]
547    fn test_contains() {
548        let mut trie = CompressedTrie::new();
549        trie.insert("foo", 1);
550        assert!(trie.contains("foo"));
551        assert!(!trie.contains("bar"));
552    }
553
554    #[test]
555    fn test_prefix_search_with_exact_prefix_match() {
556        let mut trie = CompressedTrie::new();
557        trie.insert("test", 1);
558        trie.insert("testing", 2);
559        trie.insert("tester", 3);
560
561        // Prefix that is itself a key
562        let results = trie.prefix_search("test");
563        assert_eq!(results.len(), 3);
564    }
565
566    #[test]
567    fn test_remove_with_merge() {
568        let mut trie = CompressedTrie::new();
569        trie.insert("abc", 1);
570        trie.insert("abcdef", 2);
571        trie.insert("abcxyz", 3);
572
573        // Remove "abc", which should merge "abcdef" and "abcxyz"
574        // if the internal node has no value and one child.
575        trie.remove("abc");
576        assert_eq!(trie.get("abcdef"), Some(&2));
577        assert_eq!(trie.get("abcxyz"), Some(&3));
578        assert_eq!(trie.len(), 2);
579    }
580
581    #[test]
582    fn test_empty_string_key() {
583        let mut trie = CompressedTrie::new();
584        trie.insert("", 42);
585        assert_eq!(trie.get(""), Some(&42));
586        assert_eq!(trie.len(), 1);
587
588        trie.insert("a", 1);
589        assert_eq!(trie.len(), 2);
590        assert_eq!(trie.get(""), Some(&42));
591
592        // Prefix search with empty prefix should return everything
593        let results = trie.prefix_search("");
594        assert_eq!(results.len(), 2);
595    }
596}