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}