Skip to main content

zrx_id/id/matcher/
matches.rs

1// Copyright (c) 2025-2026 Zensical and contributors
2
3// SPDX-License-Identifier: MIT
4// All contributions are certified under the DCO
5
6// Permission is hereby granted, free of charge, to any person obtaining a copy
7// of this software and associated documentation files (the "Software"), to
8// deal in the Software without restriction, including without limitation the
9// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10// sell copies of the Software, and to permit persons to whom the Software is
11// furnished to do so, subject to the following conditions:
12
13// The above copyright notice and this permission notice shall be included in
14// all copies or substantial portions of the Software.
15
16// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
19// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
22// IN THE SOFTWARE.
23
24// ----------------------------------------------------------------------------
25
26//! Match set.
27
28mod into_iter;
29
30pub use into_iter::IntoIter;
31
32// ----------------------------------------------------------------------------
33// Structs
34// ----------------------------------------------------------------------------
35
36/// Match set.
37///
38/// This match set implementation is based on a minimal bitset implementation,
39/// that allows to efficiently manage and work with match sets and filters. It
40/// mustn't be considered a complete implementation of general purpose bitsets,
41/// but only provides the methods we need for efficient matching.
42///
43/// Using a focused implementation allows us to optimize for our specific use
44/// case, and avoids yet another dependency to manage.
45#[derive(Clone, Debug, PartialEq, Eq)]
46pub struct Matches {
47    /// Blocks of bits.
48    data: Vec<u64>,
49}
50
51// ----------------------------------------------------------------------------
52// Implementations
53// ----------------------------------------------------------------------------
54
55impl Matches {
56    /// Creates a match set.
57    ///
58    /// # Examples
59    ///
60    /// ```
61    /// use zrx_id::Matches;
62    ///
63    /// // Create match set
64    /// let matches = Matches::new();
65    /// ```
66    #[must_use]
67    pub fn new() -> Self {
68        Self::with_capacity(1)
69    }
70
71    /// Creates a match set with the given capacity.
72    ///
73    /// # Examples
74    ///
75    /// ```
76    /// use zrx_id::Matches;
77    ///
78    /// // Create match set with capacity
79    /// let matches = Matches::with_capacity(128);
80    /// ```
81    #[must_use]
82    pub fn with_capacity(capacity: usize) -> Self {
83        // Note that the number of bits is rounded up to the next multiple of
84        // 64, so that the bitset can be represented as a vector of 64-bit
85        // blocks. It also means that the bitset can store at least the given
86        // number of bits, but possibly more.
87        let blocks = capacity.div_ceil(64);
88        Self { data: vec![0; blocks] }
89    }
90
91    /// Returns whether the match set contains the given match.
92    ///
93    /// # Panics
94    ///
95    /// Panics if the index is out of bounds.
96    ///
97    /// # Examples
98    ///
99    /// ```
100    /// use zrx_id::Matches;
101    ///
102    /// // Create match set
103    /// let matches = Matches::from_iter([1]);
104    ///
105    /// // Ensure presence of matches
106    /// assert_eq!(matches.contains(0), false);
107    /// assert_eq!(matches.contains(1), true);
108    /// ```
109    #[inline]
110    #[must_use]
111    pub fn contains(&self, index: usize) -> bool {
112        (self.data[index >> 6] & mask(index)) != 0
113    }
114
115    /// Adds a match to the match set.
116    ///
117    /// # Examples
118    ///
119    /// ```
120    /// use zrx_id::Matches;
121    ///
122    /// // Create match set
123    /// let mut matches = Matches::new();
124    ///
125    /// // Add match to set
126    /// matches.add(0);
127    /// ```
128    #[inline]
129    pub fn add(&mut self, index: usize) {
130        let block = self.resolve(index);
131        self.data[block] |= mask(index);
132    }
133
134    /// Clears all matches in the match set.
135    ///
136    /// # Examples
137    ///
138    /// ```
139    /// use zrx_id::Matches;
140    ///
141    /// // Create match set
142    /// let mut matches = Matches::from_iter([0, 1, 2]);
143    ///
144    /// // Remove all matches
145    /// matches.clear();
146    /// assert!(matches.is_empty());
147    /// ```
148    #[inline]
149    pub fn clear(&mut self) {
150        self.data.fill(0);
151    }
152
153    /// Computes the union with the given match set.
154    ///
155    /// # Examples
156    ///
157    /// ```
158    /// use zrx_id::Matches;
159    ///
160    /// // Create two match sets
161    /// let mut a = Matches::from_iter([0, 1]);
162    /// let mut b = Matches::from_iter([1, 2]);
163    ///
164    /// // Create union of match sets
165    /// a.union(&b);
166    /// assert_eq!(a, Matches::from_iter([0, 1, 2]));
167    /// ```
168    pub fn union(&mut self, other: &Self) {
169        for (a, b) in self.data.iter_mut().zip(&other.data) {
170            *a |= *b;
171        }
172    }
173
174    /// Computes the intersection with the given match set.
175    ///
176    /// # Examples
177    ///
178    /// ```
179    /// use zrx_id::Matches;
180    ///
181    /// // Create two match sets
182    /// let mut a = Matches::from_iter([0, 1]);
183    /// let mut b = Matches::from_iter([1, 2]);
184    ///
185    /// // Create intersection of match sets
186    /// a.intersect(&b);
187    /// assert_eq!(a, Matches::from_iter([1]));
188    /// ```
189    pub fn intersect(&mut self, other: &Self) {
190        for (a, b) in self.data.iter_mut().zip(&other.data) {
191            *a &= *b;
192        }
193    }
194
195    /// Returns whether any of the given matches is present.
196    ///
197    /// # Examples
198    ///
199    /// ```
200    /// use zrx_id::Matches;
201    ///
202    /// // Create two match sets
203    /// let mut a = Matches::from_iter([0, 1]);
204    /// let mut b = Matches::from_iter([1, 2]);
205    ///
206    /// // Ensure presence of matches
207    /// assert!(b.has_any(&a));
208    /// ```
209    #[inline]
210    #[must_use]
211    pub fn has_any(&self, other: &Self) -> bool {
212        let mut iter = self.data.iter().zip(&other.data);
213        iter.any(|(a, b)| (*a & *b) != 0)
214    }
215
216    /// Returns whether the given matches are all present.
217    ///
218    /// # Examples
219    ///
220    /// ```
221    /// use zrx_id::Matches;
222    ///
223    /// // Create two match sets
224    /// let mut a = Matches::from_iter([0, 1]);
225    /// let mut b = Matches::from_iter([0, 1, 2]);
226    ///
227    /// // Ensure presence of matches
228    /// assert!(b.has_all(&a));
229    /// ```
230    #[inline]
231    #[must_use]
232    pub fn has_all(&self, other: &Self) -> bool {
233        let mut iter = self.data.iter().zip(&other.data);
234        iter.all(|(a, b)| (*a & *b) == *b)
235    }
236
237    /// Resolve the block for the given match.
238    ///
239    /// This method ensures that the match set has enough blocks to accommodate
240    /// the given match, resizing the underlying vector if necessary.
241    fn resolve(&mut self, index: usize) -> usize {
242        let block = index >> 6;
243        if block >= self.data.len() {
244            let blocks = block + 1;
245            self.data.resize(blocks, 0);
246        }
247        block
248    }
249}
250
251#[allow(clippy::must_use_candidate)]
252impl Matches {
253    /// Returns the number of matches.
254    #[inline]
255    pub fn len(&self) -> usize {
256        let iter = self.data.iter();
257        iter.map(|block| block.count_ones() as usize).sum()
258    }
259
260    /// Returns whether there are any matches.
261    #[inline]
262    pub fn is_empty(&self) -> bool {
263        self.data.iter().all(|&block| block == 0)
264    }
265}
266
267// ----------------------------------------------------------------------------
268// Trait implementations
269// ----------------------------------------------------------------------------
270
271impl FromIterator<usize> for Matches {
272    /// Creates a match set from an iterator.
273    ///
274    /// # Examples
275    ///
276    /// ```
277    /// use zrx_id::Matches;
278    ///
279    /// // Create match set from iterator
280    /// let matches = Matches::from_iter([0, 1]);
281    /// ```
282    #[inline]
283    fn from_iter<T>(iter: T) -> Self
284    where
285        T: IntoIterator<Item = usize>,
286    {
287        let mut matches = Matches::new();
288        for index in iter {
289            matches.add(index);
290        }
291        matches
292    }
293}
294
295// ----------------------------------------------------------------------------
296
297impl Default for Matches {
298    /// Creates a match set.
299    ///
300    /// # Examples
301    ///
302    /// ```
303    /// use zrx_id::Matches;
304    ///
305    /// // Create match set
306    /// let matches = Matches::default();
307    /// ```
308    fn default() -> Self {
309        Self::new()
310    }
311}
312
313// ----------------------------------------------------------------------------
314// Functions
315// ----------------------------------------------------------------------------
316
317/// Returns the mask for the given index.
318#[inline]
319const fn mask(index: usize) -> u64 {
320    1 << (index & 63)
321}