1use crate::Interval;
2
3#[derive(Debug, Clone)]
7pub struct IntervalSet {
8 intervals: Vec<Interval>,
9 offsets: Vec<u32>,
10 size: u32,
11}
12
13impl IntervalSet {
14 #[must_use]
15 pub(crate) fn new(intervals: Vec<Interval>) -> IntervalSet {
16 let mut offsets = vec![0];
17 offsets.reserve_exact(intervals.len());
18 let mut size = 0;
19 #[allow(clippy::arithmetic_side_effects)]
21 for (left, right) in &intervals {
22 size += *right - *left + 1;
23 offsets.push(size);
24 }
25 IntervalSet {
26 intervals,
27 offsets,
28 size,
29 }
30 }
31
32 #[inline]
45 #[must_use]
46 pub fn len(&self) -> usize {
47 self.size as usize
48 }
49
50 #[inline]
65 #[must_use]
66 pub const fn is_empty(&self) -> bool {
67 self.size == 0
68 }
69
70 #[inline]
84 #[must_use]
85 pub fn contains(&self, codepoint: impl Into<u32>) -> bool {
86 self.index_of(codepoint.into()).is_some()
87 }
88
89 #[inline]
103 #[must_use]
104 pub fn codepoint_at(&self, index: u32) -> Option<u32> {
105 if index >= self.size {
106 return None;
107 }
108 #[allow(clippy::arithmetic_side_effects)]
111 let current = self.offsets.partition_point(|&offset| offset <= index) - 1;
112 #[allow(clippy::arithmetic_side_effects)]
114 Some(self.intervals[current].0 + index - self.offsets[current])
115 }
116
117 #[inline]
131 #[must_use]
132 pub fn index_of(&self, codepoint: impl Into<u32>) -> Option<u32> {
133 let codepoint = codepoint.into();
134 let idx = self
136 .intervals
137 .partition_point(|&(left, _)| left <= codepoint);
138 if idx == 0 {
139 return None;
140 }
141 #[allow(clippy::arithmetic_side_effects)]
143 let (left, right) = self.intervals[idx - 1];
144 if codepoint <= right {
145 #[allow(clippy::arithmetic_side_effects)]
147 Some(self.offsets[idx - 1] + (codepoint - left))
148 } else {
149 None
150 }
151 }
152
153 #[inline]
170 #[must_use]
171 pub fn index_above(&self, codepoint: impl Into<u32>) -> u32 {
172 let codepoint = codepoint.into();
173 let idx = self
175 .intervals
176 .partition_point(|&(left, _)| left <= codepoint);
177 if idx > 0 {
178 #[allow(clippy::arithmetic_side_effects)]
180 let (left, right) = self.intervals[idx - 1];
181 if codepoint <= right {
182 #[allow(clippy::arithmetic_side_effects)]
184 return self.offsets[idx - 1] + (codepoint - left);
185 }
186 }
187 self.offsets[idx]
190 }
191
192 pub fn iter(&self) -> Codepoints<'_> {
210 fn expand((left, right): Interval) -> core::ops::RangeInclusive<u32> {
211 left..=right
212 }
213 let expand: Expand = expand;
214 Codepoints(self.intervals.iter().copied().flat_map(expand))
215 }
216}
217
218type Expand = fn(Interval) -> core::ops::RangeInclusive<u32>;
219
220#[derive(Debug, Clone)]
222pub struct Codepoints<'a>(
223 core::iter::FlatMap<
224 core::iter::Copied<core::slice::Iter<'a, Interval>>,
225 core::ops::RangeInclusive<u32>,
226 Expand,
227 >,
228);
229
230impl Iterator for Codepoints<'_> {
231 type Item = u32;
232
233 #[inline]
234 fn next(&mut self) -> Option<u32> {
235 self.0.next()
236 }
237}
238
239impl DoubleEndedIterator for Codepoints<'_> {
240 #[inline]
241 fn next_back(&mut self) -> Option<u32> {
242 self.0.next_back()
243 }
244}
245
246impl<'a> IntoIterator for &'a IntervalSet {
247 type Item = u32;
248 type IntoIter = Codepoints<'a>;
249
250 #[inline]
251 fn into_iter(self) -> Codepoints<'a> {
252 self.iter()
253 }
254}
255
256#[cfg(test)]
257mod tests {
258 use super::*;
259 use crate::{UnicodeCategory, UnicodeVersion};
260 use test_case::test_case;
261
262 fn uppercase_letters() -> IntervalSet {
266 UnicodeVersion::V15_0_0
267 .query()
268 .include_categories(UnicodeCategory::UPPERCASE_LETTER)
269 .interval_set()
270 .expect("Invalid query input")
271 }
272
273 #[test_case(vec![(1, 1)])]
274 #[test_case(vec![])]
275 fn test_index_not_present(intervals: Vec<Interval>) {
276 assert!(IntervalSet::new(intervals).index_of(0_u32).is_none());
277 }
278
279 #[test_case(vec![], 1, None)]
280 #[test_case(vec![(1, 10)], 11, None)]
281 fn test_get(intervals: Vec<Interval>, index: u32, expected: Option<u32>) {
282 assert_eq!(IntervalSet::new(intervals).codepoint_at(index), expected);
283 }
284
285 #[test_case(vec![(1, 10)], 1, 0)]
286 #[test_case(vec![(1, 10)], 2, 1)]
287 #[test_case(vec![(1, 10)], 100, 10)]
288 fn test_index_above(intervals: Vec<Interval>, index: u32, expected: u32) {
289 assert_eq!(IntervalSet::new(intervals).index_above(index), expected);
290 }
291
292 #[test_case('Z' as u32, 25; "In the set")]
293 #[test_case('b' as u32, 26; "Not in the set")]
294 #[test_case(125218, 1831; "Greater than all")]
295 fn test_index_above_with_uppercase_letters(codepoint: u32, expected: u32) {
296 let interval_set = uppercase_letters();
297 assert_eq!(interval_set.index_above(codepoint), expected);
298 }
299
300 #[test_case('C', true)]
301 #[test_case('a', false)]
302 fn test_contains(codepoint: char, expected: bool) {
303 let interval_set = uppercase_letters();
304 assert_eq!(interval_set.contains(codepoint), expected);
305 }
306
307 #[test_case(10, Some('K' as u32); "Look from left")]
308 #[test_case(27, Some('Á' as u32); "Look from right")]
309 #[test_case(1830, Some(125217); "Max codepoint in the set")]
310 #[test_case(10000, None)]
311 #[test_case(u32::MAX, None)]
312 fn test_codepoint_at(index: u32, expected: Option<u32>) {
313 let interval_set = uppercase_letters();
314 assert_eq!(interval_set.codepoint_at(index), expected);
315 }
316
317 #[test]
318 fn test_codepoint_at_empty_set() {
319 let interval_set = IntervalSet::new(vec![]);
320 assert!(interval_set.codepoint_at(0).is_none());
321 }
322
323 #[test]
326 fn test_lookups_against_oracle() {
327 let intervals = vec![(1, 3), (10, 12), (20, 20), (100, 200)];
328 let set = IntervalSet::new(intervals.clone());
329 let flat: Vec<u32> = intervals
331 .iter()
332 .flat_map(|(left, right)| *left..=*right)
333 .collect();
334 let total = u32::try_from(flat.len()).expect("fits in u32");
335
336 for (index, expected) in (0u32..).zip(flat.iter()) {
338 assert_eq!(
339 set.codepoint_at(index),
340 Some(*expected),
341 "codepoint_at({index})"
342 );
343 }
344 assert_eq!(set.codepoint_at(total), None);
345
346 for codepoint in 0..=210_u32 {
348 let expected_index = (0u32..)
349 .zip(flat.iter())
350 .find(|(_, &c)| c == codepoint)
351 .map(|(index, _)| index);
352 assert_eq!(
353 set.index_of(codepoint),
354 expected_index,
355 "index_of({codepoint})"
356 );
357 assert_eq!(
358 set.contains(codepoint),
359 expected_index.is_some(),
360 "contains({codepoint})"
361 );
362 let expected_above = (0u32..)
364 .zip(flat.iter())
365 .find(|(_, &c)| c >= codepoint)
366 .map_or(total, |(index, _)| index);
367 assert_eq!(
368 set.index_above(codepoint),
369 expected_above,
370 "index_above({codepoint})"
371 );
372 }
373 }
374
375 #[test_case('K' as u32, Some(10); "Look from left")]
376 #[test_case('Á' as u32, Some(27); "Look from right")]
377 #[test_case(125184, Some(1797))]
378 #[test_case(5, None)]
379 fn test_index_of(codepoint: u32, expected: Option<u32>) {
380 let interval_set = uppercase_letters();
381 assert_eq!(interval_set.index_of(codepoint), expected);
382 }
383
384 #[test]
385 fn test_iter() {
386 let intervals = crate::query()
387 .include_categories(UnicodeCategory::LOWERCASE_LETTER)
388 .intervals()
389 .expect("Invalid query input");
390 let interval_set = IntervalSet::new(intervals);
391 let codepoints: Vec<_> = interval_set.iter().collect();
392 let mut expected = Vec::with_capacity(interval_set.len());
393 for (left, right) in
394 UnicodeVersion::latest().intervals_for(UnicodeCategory::LOWERCASE_LETTER)
395 {
396 for codepoint in *left..=*right {
397 expected.push(codepoint);
398 }
399 }
400 assert_eq!(codepoints, expected);
401 assert_eq!(interval_set.len(), codepoints.len());
402 assert!(!interval_set.is_empty());
403 }
404
405 #[test]
406 fn test_iter_rev() {
407 let interval_set = uppercase_letters();
408 let mut iter = interval_set.iter().rev();
409 assert_eq!(iter.next(), Some(125217));
410 }
411
412 #[test]
413 fn test_into_iterator_for_ref() {
414 let interval_set = IntervalSet::new(vec![(65, 67), (70, 70)]);
415 let collected: Vec<u32> = (&interval_set).into_iter().collect();
416 assert_eq!(collected, vec![65, 66, 67, 70]);
417 let mut via_for_loop = Vec::new();
418 for codepoint in &interval_set {
419 via_for_loop.push(codepoint);
420 }
421 assert_eq!(via_for_loop, vec![65, 66, 67, 70]);
422 }
423
424 #[test]
425 #[allow(clippy::redundant_clone)]
426 fn test_interval_set_traits() {
427 let interval_set = IntervalSet::new(vec![(0, 1)]);
428 let _ = interval_set.clone();
429 assert_eq!(
430 format!("{interval_set:?}"),
431 "IntervalSet { intervals: [(0, 1)], offsets: [0, 2], size: 2 }"
432 );
433 }
434}