Skip to main content

grafeo_core/execution/
selection.rs

1//! SelectionVector for filtering.
2
3/// A selection vector indicating which rows are active.
4///
5/// Used for efficient filtering without copying data.
6#[derive(Debug, Clone)]
7pub struct SelectionVector {
8    /// Indices of selected rows.
9    indices: Vec<u16>,
10}
11
12impl SelectionVector {
13    /// Maximum capacity (limited to u16 for space efficiency).
14    pub const MAX_CAPACITY: usize = u16::MAX as usize;
15
16    /// Creates a new selection vector selecting all rows up to count.
17    ///
18    /// # Panics
19    ///
20    /// Panics if `count` exceeds `SelectionVector::MAX_CAPACITY` (65535).
21    #[must_use]
22    pub fn new_all(count: usize) -> Self {
23        assert!(count <= Self::MAX_CAPACITY);
24        Self {
25            // reason: count <= MAX_CAPACITY (65535) asserted above, fits u16
26            #[allow(clippy::cast_possible_truncation)]
27            indices: (0..count as u16).collect(),
28        }
29    }
30
31    /// Creates a new empty selection vector.
32    #[must_use]
33    pub fn new_empty() -> Self {
34        Self {
35            indices: Vec::new(),
36        }
37    }
38
39    /// Creates a new selection vector with the given capacity.
40    #[must_use]
41    pub fn with_capacity(capacity: usize) -> Self {
42        Self {
43            indices: Vec::with_capacity(capacity.min(Self::MAX_CAPACITY)),
44        }
45    }
46
47    /// Creates a selection vector from a predicate.
48    ///
49    /// Selects all indices where the predicate returns true.
50    #[must_use]
51    pub fn from_predicate<F>(count: usize, predicate: F) -> Self
52    where
53        F: Fn(usize) -> bool,
54    {
55        let indices: Vec<u16> = (0..count)
56            .filter(|&i| predicate(i))
57            // reason: i < count which is bounded by MAX_CAPACITY (65535), fits u16
58            .map(|i| {
59                // reason: i < count, bounded by MAX_CAPACITY (65535), fits u16
60                #[allow(clippy::cast_possible_truncation)]
61                let idx = i as u16;
62                idx
63            })
64            .collect();
65        Self { indices }
66    }
67
68    /// Returns the number of selected rows.
69    #[must_use]
70    pub fn len(&self) -> usize {
71        self.indices.len()
72    }
73
74    /// Returns true if no rows are selected.
75    #[must_use]
76    pub fn is_empty(&self) -> bool {
77        self.indices.is_empty()
78    }
79
80    /// Gets the actual row index at position.
81    #[must_use]
82    pub fn get(&self, position: usize) -> Option<usize> {
83        self.indices.get(position).map(|&i| i as usize)
84    }
85
86    /// Pushes a new index.
87    ///
88    /// # Panics
89    ///
90    /// Panics if `index` exceeds `SelectionVector::MAX_CAPACITY` (65535).
91    pub fn push(&mut self, index: usize) {
92        assert!(index <= Self::MAX_CAPACITY);
93        // reason: index <= MAX_CAPACITY (65535) asserted above, fits u16
94        #[allow(clippy::cast_possible_truncation)]
95        self.indices.push(index as u16);
96    }
97
98    /// Returns the indices as a slice.
99    #[must_use]
100    pub fn as_slice(&self) -> &[u16] {
101        &self.indices
102    }
103
104    /// Clears all selections.
105    pub fn clear(&mut self) {
106        self.indices.clear();
107    }
108
109    /// Filters this selection by another predicate.
110    ///
111    /// Returns a new selection containing only indices that pass the predicate.
112    #[must_use]
113    pub fn filter<F>(&self, predicate: F) -> Self
114    where
115        F: Fn(usize) -> bool,
116    {
117        let indices: Vec<u16> = self
118            .indices
119            .iter()
120            .copied()
121            .filter(|&i| predicate(i as usize))
122            .collect();
123        Self { indices }
124    }
125
126    /// Computes the intersection of two selection vectors.
127    #[must_use]
128    pub fn intersect(&self, other: &Self) -> Self {
129        // Assumes both are sorted (which they typically are)
130        let mut result = Vec::new();
131        let mut i = 0;
132        let mut j = 0;
133
134        while i < self.indices.len() && j < other.indices.len() {
135            match self.indices[i].cmp(&other.indices[j]) {
136                std::cmp::Ordering::Less => i += 1,
137                std::cmp::Ordering::Greater => j += 1,
138                std::cmp::Ordering::Equal => {
139                    result.push(self.indices[i]);
140                    i += 1;
141                    j += 1;
142                }
143            }
144        }
145
146        Self { indices: result }
147    }
148
149    /// Computes the union of two selection vectors.
150    #[must_use]
151    pub fn union(&self, other: &Self) -> Self {
152        let mut result = Vec::new();
153        let mut i = 0;
154        let mut j = 0;
155
156        while i < self.indices.len() && j < other.indices.len() {
157            match self.indices[i].cmp(&other.indices[j]) {
158                std::cmp::Ordering::Less => {
159                    result.push(self.indices[i]);
160                    i += 1;
161                }
162                std::cmp::Ordering::Greater => {
163                    result.push(other.indices[j]);
164                    j += 1;
165                }
166                std::cmp::Ordering::Equal => {
167                    result.push(self.indices[i]);
168                    i += 1;
169                    j += 1;
170                }
171            }
172        }
173
174        result.extend_from_slice(&self.indices[i..]);
175        result.extend_from_slice(&other.indices[j..]);
176
177        Self { indices: result }
178    }
179
180    /// Returns an iterator over selected indices.
181    pub fn iter(&self) -> impl Iterator<Item = usize> + '_ {
182        self.indices.iter().map(|&i| i as usize)
183    }
184
185    /// Checks if a given index is in the selection.
186    #[must_use]
187    pub fn contains(&self, index: usize) -> bool {
188        if index > u16::MAX as usize {
189            return false;
190        }
191        // Since indices are typically sorted, use binary search
192        // reason: index <= u16::MAX checked above
193        #[allow(clippy::cast_possible_truncation)]
194        self.indices.binary_search(&(index as u16)).is_ok()
195    }
196}
197
198impl Default for SelectionVector {
199    fn default() -> Self {
200        Self::new_empty()
201    }
202}
203
204impl IntoIterator for SelectionVector {
205    type Item = usize;
206    type IntoIter = std::iter::Map<std::vec::IntoIter<u16>, fn(u16) -> usize>;
207
208    fn into_iter(self) -> Self::IntoIter {
209        self.indices.into_iter().map(|i| i as usize)
210    }
211}
212
213#[cfg(test)]
214mod tests {
215    use super::*;
216
217    #[test]
218    fn test_selection_all() {
219        let sel = SelectionVector::new_all(10);
220        assert_eq!(sel.len(), 10);
221
222        for i in 0..10 {
223            assert_eq!(sel.get(i), Some(i));
224        }
225    }
226
227    #[test]
228    fn test_selection_from_predicate() {
229        let sel = SelectionVector::from_predicate(10, |i| i % 2 == 0);
230
231        assert_eq!(sel.len(), 5);
232        assert_eq!(sel.get(0), Some(0));
233        assert_eq!(sel.get(1), Some(2));
234        assert_eq!(sel.get(2), Some(4));
235    }
236
237    #[test]
238    fn test_selection_filter() {
239        let sel = SelectionVector::new_all(10);
240        let filtered = sel.filter(|i| i >= 5);
241
242        assert_eq!(filtered.len(), 5);
243        assert_eq!(filtered.get(0), Some(5));
244    }
245
246    #[test]
247    fn test_selection_intersect() {
248        let sel1 = SelectionVector::from_predicate(10, |i| i % 2 == 0); // 0, 2, 4, 6, 8
249        let sel2 = SelectionVector::from_predicate(10, |i| i % 3 == 0); // 0, 3, 6, 9
250
251        let intersection = sel1.intersect(&sel2);
252        // Intersection: 0, 6
253
254        assert_eq!(intersection.len(), 2);
255        assert_eq!(intersection.get(0), Some(0));
256        assert_eq!(intersection.get(1), Some(6));
257    }
258
259    #[test]
260    fn test_selection_union() {
261        let sel1 = SelectionVector::from_predicate(10, |i| i == 1 || i == 3); // 1, 3
262        let sel2 = SelectionVector::from_predicate(10, |i| i == 2 || i == 3); // 2, 3
263
264        let union = sel1.union(&sel2);
265        // Union: 1, 2, 3
266
267        assert_eq!(union.len(), 3);
268        assert_eq!(union.get(0), Some(1));
269        assert_eq!(union.get(1), Some(2));
270        assert_eq!(union.get(2), Some(3));
271    }
272
273    #[test]
274    fn test_selection_iterator() {
275        let sel = SelectionVector::from_predicate(5, |i| i < 3);
276        let collected: Vec<_> = sel.iter().collect();
277
278        assert_eq!(collected, vec![0, 1, 2]);
279    }
280}