augmented_rbtree/search.rs
1//! Custom, Zero-Allocation Search and Traversal for Augmented Red-Black Trees.
2//!
3//! This module provides highly optimized primitives for executing targeted queries over an
4//! augmented binary search tree. Instead of performing traditional, unguided tree traversals,
5//! this module abstracts structural pruning logic to allow fast, custom-indexed lookups
6//! (such as range queries, interval intersections, or weight-based selections).
7//!
8//! # Architecture & Pruning Philosophy
9//!
10//! Traditional search algorithms tightly couple tree geometry with search criteria. This module
11//! breaks that coupling using a decoupled, policy-driven architecture via the [`InOrderPruningPolicy`] trait:
12//!
13//! 1. **Tree Geometry**: Managed natively by the stateful [`InOrderIter`].
14//! 2. **Pruning Strategy**: Dictated by an external component implementing [`InOrderPruningPolicy`].
15//! Before descending into a subtree, the iterator asks the policy whether that branch should
16//! be evaluated or structurally pruned.
17//!
18//! # Memory & Performance Characteristics
19//!
20//! - **Space Complexity**: `O(1)` auxiliary space. The traversal uses a persistent, re-entrant
21//! state machine ([`InOrderIter`]) tracking structural geometry and the current [`TraversalPhase`].
22//! It requires **zero allocations**, avoiding heap-allocated backtracking vectors or call stacks.
23//! - **Time Complexity**: Bounds range from `O(log N)` for highly constrained pruning policies
24//! (e.g., singular key lookups) up to `O(N)` for exhaustive scans.
25//!
26//! # Subtree Constraints & Re-entrancy
27//!
28//! To support flexible querying, [`InOrderIter`] can be bound to a specific `subtree_root`. The iterator
29//! enforces strict structural boundaries: it will **never drift higher** than or escape the bounds of
30//! the initialized subtree. Because it preserves its exact geometric location across invocations,
31//! it can be safely paused, resumed, or used to build higher-level streaming interfaces.
32//!
33//! # Examples
34//!
35//! ```
36//! # use augmented_rbtree::{InOrderPruningPolicy, InOrderIter};
37//! // Define a policy that skips metadata categories
38//! struct MyCustomSearchPolicy;
39//!
40//! impl InOrderPruningPolicy<u64, String, u32> for MyCustomSearchPolicy {
41//! fn is_match(&self, _k: &u64, _v: &String, stats: &u32) -> bool {
42//! *stats > 100 // Only match nodes with high augmented weight
43//! }
44//! fn should_explore_left(&self, left: (&u64, &String, &u32), _: (&u64, &String, &u32)) -> bool {
45//! *left.2 > 50 // Prune left child if its subtree total weight is too low
46//! }
47//! fn should_explore_right(&self, right: (&u64, &String, &u32), _: (&u64, &String, &u32)) -> bool {
48//! true // Always check right
49//! }
50//! }
51//! ```
52
53use core::{borrow::Borrow, marker::PhantomData};
54
55use crate::{
56 AugmentedRBTreeInt, alloc_proxy::proxy::Allocator, augmented_rbtree::TreeLocation,
57 cursor::NavCursor, node::internal_details::NodeRef, policy,
58};
59
60#[derive(Clone, Copy, Debug, PartialEq, Eq)]
61enum TraversalPhase {
62 Above,
63 Left,
64 Right,
65}
66
67/// A policy trait that separates structural pruning rules from the tree architecture.
68pub trait InOrderPruningPolicy<K, V, S> {
69 /// Evaluates if the current node satisfies the lookup constraints.
70 fn is_match(&self, key: &K, value: &V, stats: &S) -> bool;
71
72 /// Determines if the left child branch should be explored or pruned.
73 fn should_explore_left(&self, left: (&K, &V, &S), current: (&K, &V, &S)) -> bool;
74
75 /// Determines if the right child branch should be explored or pruned.
76 fn should_explore_right(&self, right: (&K, &V, &S), current: (&K, &V, &S)) -> bool;
77}
78
79/// A stateful, direction-aware iterator that performs an in-order Depth-First Search (DFS)
80/// over an augmented binary search tree.
81///
82/// This iterator leverages the physical geometry of the tree structure combined with a persistent
83/// direction state to avoid allocating a backtracking vector or an internal node call stack.
84/// It is completely re-entrant, allowing it to yield intermediate values safely across successive
85/// invocations of `.next()`.
86///
87/// To protect against drifting out of bounds during a targeted search, it enforces a structural
88/// boundary check that prevents the cursor from overshooting the original subtree root node.
89///
90/// # Type Parameters
91/// * `K` - The tree node key type.
92/// * `V` - The tree node value type.
93/// * `S` - The augmented subtree statistics type used by the pruning policy.
94/// * `P` - A type implementing [`InOrderPruningPolicy`] to dictate matching and pruning criteria.
95
96#[derive(Debug)]
97pub struct InOrderIter<'a, K, V, S, P>
98where
99 P: InOrderPruningPolicy<K, V, S>,
100{
101 cur: Option<NodeRef<K, V, S>>,
102 policy: P,
103 subtree_root: Option<NodeRef<K, V, S>>,
104 direction: TraversalPhase,
105 _marker: PhantomData<&'a (K, V, S)>,
106}
107
108impl<'a, K, V, S, P> Iterator for InOrderIter<'a, K, V, S, P>
109where
110 P: InOrderPruningPolicy<K, V, S>,
111{
112 type Item = (&'a K, &'a V, &'a S);
113
114 fn next(&mut self) -> Option<Self::Item> {
115 loop {
116 // Retrieve current node details. If None, the cursor space has been exhausted.
117 let node = self.cur?;
118
119 let (key, value, stats) = unsafe { (node.key(), node.value(), node.stats()) };
120
121 match self.direction {
122 TraversalPhase::Above => {
123 // Check if we can traverse the left subtree first
124 if let Some(left_node) = node.left() {
125 let (left_key, left_value, left_stats) =
126 unsafe { (left_node.key(), left_node.value(), left_node.stats()) };
127 if self.policy.should_explore_left(
128 (left_key, left_value, left_stats),
129 (key, value, stats),
130 ) {
131 self.cur = Some(left_node);
132 self.direction = TraversalPhase::Above; // Reset direction for the left sub-hierarchy
133 continue;
134 }
135 }
136 // Left subtree is absent or pruned. We say that we returned to the current from left
137 self.direction = TraversalPhase::Left;
138 }
139
140 TraversalPhase::Left => {
141 // We hit the current node from the left child, so we can evaluate it now
142 // The policy is to yield the node coming from the bottom
143
144 let is_matching_node = self.policy.is_match(key, value, stats);
145
146 if let Some(right_node) = node.right() {
147 let (right_key, right_value, right_stats) =
148 unsafe { (right_node.key(), right_node.value(), right_node.stats()) };
149 if self.policy.should_explore_right(
150 (right_key, right_value, right_stats),
151 (key, value, stats),
152 ) {
153 // OK this tells me I can resume search in the right subtree.
154 self.cur = Some(right_node);
155 self.direction = TraversalPhase::Above; // Reset direction for the right sub-hierarchy
156
157 // Yield the current matching parent node.
158 // The cursor is staged inside the fresh right subtree for the next loop.
159 if is_matching_node {
160 return Some((key, value, stats));
161 }
162 continue;
163 }
164 }
165
166 // I am not able to move to the right subtree, so I need to ascend and update the direction state
167 if is_matching_node {
168 self.ascend_and_update_state();
169 return Some((key, value, stats));
170 }
171
172 self.ascend_and_update_state();
173 }
174
175 TraversalPhase::Right => {
176 // Done with both subtrees, we yielded the current node and now we need to ascend to the parent
177 self.ascend_and_update_state();
178 }
179 }
180 }
181 }
182}
183
184impl<K, V, S, P> InOrderIter<'_, K, V, S, P>
185where
186 P: InOrderPruningPolicy<K, V, S>,
187{
188 /// Constructs a new `InOrderIter` starting at the provided node position.
189 ///
190 /// This method automatically captures the starting node position as the structural ceiling
191 /// for the traversal, ensuring it does not overshoot into adjacent tree families.
192 pub fn new<A, R, Q>(
193 tree: &AugmentedRBTreeInt<K, V, S, A, R>,
194 location: TreeLocation<&Q>,
195 policy: P,
196 ) -> Self
197 where
198 A: Allocator,
199 R: policy::internal_details::TreePolicy<K = K, V = V, S = S>,
200 K: Borrow<Q> + Ord,
201 Q: Ord,
202 {
203 let cur = tree.get_tree_location(location);
204 Self {
205 cur,
206 policy,
207 subtree_root: cur, // Directly passes the option without conditional blocks
208 direction: TraversalPhase::Above,
209 _marker: PhantomData,
210 }
211 }
212
213 /// Constructs a new `InOrderIter` starting at the node currently pointed to by a [`NavCursor`].
214 ///
215 /// This allows power-users to initialize a highly customized pruning search originating from
216 /// any arbitrary bookmark or position in the tree structure.
217 ///
218 pub fn from_cursor(cursor: &NavCursor<'_, K, V, S>, policy: P) -> Self {
219 // Safe bridge: Unpack the internal Option<NodeRef> from the public cursor
220 let starting_node = cursor.current;
221
222 Self {
223 cur: starting_node,
224 policy,
225 subtree_root: starting_node,
226 direction: TraversalPhase::Above,
227 _marker: PhantomData,
228 }
229 }
230
231 /// Shifts the cursor upward by exactly one level while protecting against overshooting
232 /// the designated subtree root boundary.
233 fn ascend_and_update_state(&mut self) {
234 // Stop instantly if the current node matches our initial subtree ceiling.
235 if self.cur == self.subtree_root {
236 self.cur = None; // O(1) instant termination
237 return;
238 }
239
240 if let Some(node) = self.cur {
241 // Invariant: Because we checked the subtree_root boundary above,
242 // this node is guaranteed to have a parent in a valid tree.
243 let parent = node
244 .parent()
245 .expect("Invariant violation: Node must have a parent");
246
247 if parent.left() == Some(node) {
248 self.direction = TraversalPhase::Left;
249 } else {
250 self.direction = TraversalPhase::Right;
251 }
252
253 self.cur = Some(parent);
254 }
255 }
256}