Skip to main content

rexact_core/
charset.rs

1//! Interval sets of code points and partitions of the alphabet.
2//!
3//! Invariant: `ranges` is sorted, non-empty ranges are inclusive, and no two
4//! ranges overlap or touch, so equal sets have one representation. All values
5//! are at most `0x10FFFF`, so `+ 1` cannot overflow `u32`.
6
7/// A set of code points stored as normalised inclusive ranges.
8#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug, Default)]
9pub struct CharSet {
10    ranges: Vec<(u32, u32)>,
11}
12
13impl CharSet {
14    /// The empty set.
15    pub fn empty() -> Self {
16        Self::default()
17    }
18
19    /// One code point.
20    pub fn single(c: u32) -> Self {
21        Self {
22            ranges: vec![(c, c)],
23        }
24    }
25
26    /// Inclusive range; empty when `lo > hi`.
27    pub fn range(lo: u32, hi: u32) -> Self {
28        if lo > hi {
29            Self::empty()
30        } else {
31            Self {
32                ranges: vec![(lo, hi)],
33            }
34        }
35    }
36
37    /// Normalise arbitrary ranges (any order, overlapping, touching).
38    pub fn from_ranges<I: IntoIterator<Item = (u32, u32)>>(it: I) -> Self {
39        let mut v: Vec<(u32, u32)> = it.into_iter().filter(|(l, h)| l <= h).collect();
40        v.sort_unstable();
41        let mut out: Vec<(u32, u32)> = Vec::with_capacity(v.len());
42        for (lo, hi) in v {
43            match out.last_mut() {
44                Some(last) if lo <= last.1.saturating_add(1) => last.1 = last.1.max(hi),
45                _ => out.push((lo, hi)),
46            }
47        }
48        Self { ranges: out }
49    }
50
51    /// The normalised ranges.
52    pub fn ranges(&self) -> &[(u32, u32)] {
53        &self.ranges
54    }
55
56    /// True when no code point is a member.
57    pub fn is_empty(&self) -> bool {
58        self.ranges.is_empty()
59    }
60
61    /// Smallest member.
62    pub fn smallest(&self) -> Option<u32> {
63        self.ranges.first().map(|r| r.0)
64    }
65
66    /// Membership test.
67    pub fn contains(&self, c: u32) -> bool {
68        self.ranges
69            .binary_search_by(|&(lo, hi)| {
70                if c < lo {
71                    std::cmp::Ordering::Greater
72                } else if c > hi {
73                    std::cmp::Ordering::Less
74                } else {
75                    std::cmp::Ordering::Equal
76                }
77            })
78            .is_ok()
79    }
80
81    /// Number of members.
82    pub fn count(&self) -> u64 {
83        self.ranges.iter().map(|&(l, h)| u64::from(h - l) + 1).sum()
84    }
85
86    /// Set union.
87    pub fn union(&self, o: &CharSet) -> CharSet {
88        CharSet::from_ranges(self.ranges.iter().chain(o.ranges.iter()).copied())
89    }
90
91    /// Set intersection.
92    pub fn intersect(&self, o: &CharSet) -> CharSet {
93        let (mut i, mut j) = (0, 0);
94        let mut out = Vec::new();
95        while i < self.ranges.len() && j < o.ranges.len() {
96            let (a, b) = (self.ranges[i], o.ranges[j]);
97            let (lo, hi) = (a.0.max(b.0), a.1.min(b.1));
98            if lo <= hi {
99                out.push((lo, hi));
100            }
101            if a.1 < b.1 {
102                i += 1;
103            } else {
104                j += 1;
105            }
106        }
107        Self { ranges: out }
108    }
109
110    /// Members of `self` that are not in `o`.
111    pub fn difference(&self, o: &CharSet) -> CharSet {
112        let mut out = Vec::new();
113        let mut j = 0;
114        for &(lo, hi) in &self.ranges {
115            let mut cur = lo;
116            while j < o.ranges.len() && o.ranges[j].1 < cur {
117                j += 1;
118            }
119            let mut k = j;
120            let mut covered = false;
121            while k < o.ranges.len() && o.ranges[k].0 <= hi {
122                let (blo, bhi) = o.ranges[k];
123                if blo > cur {
124                    out.push((cur, blo - 1));
125                }
126                if bhi >= hi {
127                    covered = true;
128                    break;
129                }
130                cur = bhi + 1;
131                k += 1;
132            }
133            if !covered && cur <= hi {
134                out.push((cur, hi));
135            }
136        }
137        Self { ranges: out }
138    }
139}
140
141/// Split every block of `p` by membership in `s`, dropping empty pieces.
142pub fn refine(p: &[CharSet], s: &CharSet) -> Vec<CharSet> {
143    let mut out = Vec::with_capacity(p.len() + 1);
144    for b in p {
145        let inside = b.intersect(s);
146        if inside.is_empty() {
147            out.push(b.clone());
148        } else {
149            let outside = b.difference(s);
150            out.push(inside);
151            if !outside.is_empty() {
152                out.push(outside);
153            }
154        }
155    }
156    out
157}
158
159/// Common refinement of two partitions, ordered by smallest member.
160pub fn refine_all(p: &[CharSet], q: &[CharSet]) -> Vec<CharSet> {
161    let mut out = Vec::new();
162    for a in p {
163        for b in q {
164            let x = a.intersect(b);
165            if !x.is_empty() {
166                out.push(x);
167            }
168        }
169    }
170    out.sort_by_key(|s| s.smallest());
171    out
172}
173
174#[cfg(test)]
175mod tests {
176    use super::*;
177
178    fn cs(r: &[(u32, u32)]) -> CharSet {
179        CharSet::from_ranges(r.iter().copied())
180    }
181
182    #[test]
183    fn normalisation_merges_overlapping_and_touching() {
184        assert_eq!(cs(&[(5, 9), (1, 3), (4, 4), (8, 12)]).ranges(), &[(1, 12)]);
185        assert_eq!(cs(&[(1, 2), (4, 5)]).ranges(), &[(1, 2), (4, 5)]);
186        assert!(cs(&[(3, 1)]).is_empty());
187    }
188
189    #[test]
190    fn set_algebra_matches_brute_force_on_a_small_universe() {
191        // Exhaustively compare against bit-set semantics on 0..16.
192        let sets: Vec<CharSet> = (0u32..256)
193            .map(|m| {
194                CharSet::from_ranges(
195                    (0..8)
196                        .filter(|b| m >> b & 1 == 1)
197                        .map(|b| (b * 2, b * 2 + 1)),
198                )
199            })
200            .collect();
201        let bits = |s: &CharSet| (0..16u32).filter(|&c| s.contains(c)).collect::<Vec<_>>();
202        for a in sets.iter().step_by(7) {
203            for b in sets.iter().step_by(5) {
204                let (x, y) = (bits(a), bits(b));
205                let u: Vec<u32> = (0..16).filter(|c| x.contains(c) || y.contains(c)).collect();
206                let i: Vec<u32> = (0..16).filter(|c| x.contains(c) && y.contains(c)).collect();
207                let d: Vec<u32> = (0..16)
208                    .filter(|c| x.contains(c) && !y.contains(c))
209                    .collect();
210                assert_eq!(bits(&a.union(b)), u);
211                assert_eq!(bits(&a.intersect(b)), i);
212                assert_eq!(bits(&a.difference(b)), d);
213            }
214        }
215    }
216
217    #[test]
218    fn difference_handles_holes_and_full_cover() {
219        assert_eq!(
220            cs(&[(0, 100)])
221                .difference(&cs(&[(10, 20), (30, 40)]))
222                .ranges(),
223            &[(0, 9), (21, 29), (41, 100)]
224        );
225        assert!(cs(&[(5, 6)]).difference(&cs(&[(0, 10)])).is_empty());
226        assert_eq!(
227            cs(&[(1, 3), (5, 9)]).difference(&cs(&[(3, 5)])).ranges(),
228            &[(1, 2), (6, 9)]
229        );
230    }
231
232    #[test]
233    fn membership_count_and_min() {
234        let s = cs(&[(10, 12), (100, 100)]);
235        assert!(s.contains(11) && s.contains(100) && !s.contains(13) && !s.contains(9));
236        assert_eq!((s.count(), s.smallest()), (4, Some(10)));
237    }
238
239    #[test]
240    fn refinement_partitions_exactly() {
241        let all = CharSet::range(0, 99);
242        let p = refine(
243            &refine(std::slice::from_ref(&all), &CharSet::range(10, 30)),
244            &CharSet::range(20, 50),
245        );
246        let mut union = CharSet::empty();
247        for (i, a) in p.iter().enumerate() {
248            for b in &p[i + 1..] {
249                assert!(a.intersect(b).is_empty());
250            }
251            union = union.union(a);
252        }
253        assert_eq!(union, all);
254        // Blocks are sets, not intervals: {10-19} {20-30} {31-50} {0-9,51-99}.
255        assert_eq!(p.len(), 4);
256    }
257}