smt_str/alphabet/
partition.rs

1//! This module defines types for partitioning the SMT-LIB alphabet.
2//!
3//! Unlike [`Alphabet`], ranges are not compacted and can be adjacent.
4//!
5//! ## Types
6//!
7//! - [`AlphabetPartition`] represents a finite partition of the SMT-LIB alphabet into non-overlapping [`CharRange`]s.
8//! - [`AlphabetPartitionMap<T>`] extends this with values of type `T` associated to each range.
9//!
10//!
11//! ## Refinement
12//!
13//! Both struct provide a partition refinement operation.
14//! Given two partitions `P` and `Q`, the **refinement** of `P` w.r.t. `P` is a
15//! partitioning `R` that is the set of all non-empty intersections
16//!
17//! - `p ∩ q` for all `p ∈ P` and `q ∈ Q`.  
18//! - `p' ∩ q` for all `p' ∈ comp(P)` and `q ∈ Q`.  
19//! - `p ∩ q'` for all `p ∈ P` and `q' ∈ comp(Q)`.
20//!
21//! where `comp(P)` and `comp(Q)` are the complements of `P` and `Q`, respectively.
22//! The resulting ranges in `R` are disjoint, ordered, and their union is equal to the union of `P ∪ Q`.
23//!
24//! In other words, refinement splits ranges in `P` and `Q` as needed so that the resulting partition `R` respects the boundaries of both.
25//!
26//! For `AlphabetPartitionMap<T>`, the refinement operation also combines the values from both input partitions using a user-provided function `f: T × T -> T`, applied to the values associated with intersecting ranges.
27use std::{
28    collections::{btree_map, BTreeMap},
29    fmt::Display,
30};
31
32use super::CharRange;
33
34/// A partitioning of the SMT-LIB alphabet into disjoint character ranges without associated values.
35///
36/// This is a convenience wrapper around [`AlphabetPartitionMap<()>`] for value-less partitioning.
37/// See the module-level documentation for details.
38///
39/// # Example
40/// ```
41/// use smt_str::alphabet::{partition::AlphabetPartition, CharRange};
42///
43/// let mut p = AlphabetPartition::default();
44/// p.insert(CharRange::new('a', 'z')).unwrap();
45///
46/// assert_eq!(p.len(), 1);
47/// ```
48#[derive(Clone, Default, Debug)]
49pub struct AlphabetPartition {
50    map: AlphabetPartitionMap<()>,
51}
52
53impl AlphabetPartition {
54    /// Creates an empty partitioning.
55    pub fn empty() -> Self {
56        Self {
57            map: AlphabetPartitionMap::empty(),
58        }
59    }
60
61    /// Creates a partitioning with a single range.
62    ///
63    /// ```
64    /// use smt_str::alphabet::{partition::AlphabetPartition, CharRange};
65    ///
66    /// let range = CharRange::new('a', 'z');
67    /// let partitioning = AlphabetPartition::singleton(range.clone());
68    /// assert_eq!(partitioning.len(), 1);
69    /// assert!(partitioning.contains(&range));
70    /// ```
71    pub fn singleton(r: CharRange) -> Self {
72        let map = AlphabetPartitionMap::singleton(r, ());
73        Self { map }
74    }
75
76    /// Inserts the given character range into the partitioning.
77    ///
78    /// This method checks whether the given range overlaps with any existing partition.  
79    /// If it does not, the range is inserted and `Ok(())` is returned.  
80    /// If it overlaps with an existing partition, the insertion is rejected and the overlapping range is returned in `Err(...)`.
81    ///
82    /// This operation takes O(n) time, where `n` is the number of ranges in the partition.  
83    /// If the caller can guarantee that the range does not overlap, use [`insert_unchecked`](Self::insert_unchecked) for improved performance.
84    ///
85    /// # Examples
86    ///
87    /// ```
88    /// use smt_str::alphabet::{partition::AlphabetPartition, CharRange};
89    ///
90    /// let mut partitioning = AlphabetPartition::empty();
91    ///
92    /// // Insert a non-overlapping range
93    /// let range = CharRange::new('a', 'z');
94    /// assert_eq!(partitioning.insert(range.clone()), Ok(()));
95    /// assert!(partitioning.contains(&range));
96    ///
97    /// // Insert an overlapping range
98    /// let overlapping = CharRange::new('m', 'p');
99    /// assert_eq!(partitioning.insert(overlapping), Err(CharRange::new('a', 'z')));
100    /// ```
101    pub fn insert(&mut self, range: CharRange) -> Result<(), CharRange> {
102        self.map.insert(range, ())
103    }
104
105    /// Inserts the given character range into the partitioning, without checking if the partitioning is still valid.
106    /// Takes O(log n) time, where n is the number of partitions in the partitioning.
107    ///
108    /// This method must be used with caution, as it can lead to an invalid partitioning if the range overlaps with an existing partition.
109    ///
110    /// # Examples
111    ///
112    /// ```
113    /// use smt_str::alphabet::{partition::AlphabetPartition, CharRange};
114    ///
115    /// let mut partitioning = AlphabetPartition::empty();
116    /// partitioning.insert_unchecked(CharRange::new('a','z'));
117    /// assert!(partitioning.contains(&CharRange::new('a','z')));
118    ///
119    /// // This will lead to an invalid partitioning
120    /// partitioning.insert_unchecked(CharRange::new('m','p'));
121    /// assert!(partitioning.contains(&CharRange::new('m','p')));
122    /// assert!(!partitioning.valid());
123    /// ```
124    pub fn insert_unchecked(&mut self, range: CharRange) {
125        self.map.insert_unchecked(range, ());
126    }
127
128    /// Returns `true` if the given character range is explicitly contained in the partitioning.
129    /// This method checks for the presence of the exact range, not subranges.
130    ///
131    /// # Examples
132    ///
133    /// ```
134    /// use smt_str::alphabet::{partition::AlphabetPartition, CharRange};
135    ///
136    /// let range = CharRange::new('a', 'z');
137    /// let mut partitioning = AlphabetPartition::empty();
138    /// partitioning.insert_unchecked(range.clone());
139    ///
140    /// assert!(partitioning.contains(&range));
141    ///
142    /// // Subranges are not considered present
143    /// assert!(!partitioning.contains(&CharRange::new('a', 'y')));
144    /// ```
145    pub fn contains(&self, range: &CharRange) -> bool {
146        self.map.get(range).is_some()
147    }
148
149    /// Returns the number of partitions in the partitioning.
150    pub fn len(&self) -> usize {
151        self.map.len()
152    }
153
154    /// Returns true if the partitioning is empty.
155    pub fn is_empty(&self) -> bool {
156        self.map.is_empty()
157    }
158
159    /// Removes the given character range from the partitioning.
160    ///
161    /// Only removes the range if it exactly matches a partition in the set.  
162    /// Returns `true` if the range was removed, and `false` if it was not present.
163    ///
164    ///
165    /// # Examples
166    ///
167    /// ```
168    /// use smt_str::alphabet::{partition::AlphabetPartition, CharRange};
169    ///
170    /// let mut partitioning = AlphabetPartition::empty();
171    /// partitioning.insert_unchecked(CharRange::new('a', 'z'));
172    ///
173    /// assert!(partitioning.contains(&CharRange::new('a', 'z')));
174    ///
175    /// // Subranges are not considered matches
176    /// assert!(!partitioning.remove(CharRange::new('a', 'm')));
177    ///
178    /// // Exact match is required
179    /// assert!(partitioning.remove(CharRange::new('a', 'z')));
180    /// assert!(!partitioning.contains(&CharRange::new('a', 'z')));
181    /// ```
182    pub fn remove(&mut self, range: CharRange) -> bool {
183        self.map.remove(range).is_some()
184    }
185
186    /// Performs a partition refinement of this partitioning with the given partitioning.
187    /// See module-level documentation and [`AlphabetPartitionMap::refine`] for details.
188    ///
189    /// # Examples
190    ///
191    /// ```
192    /// use smt_str::alphabet::{partition::AlphabetPartition, CharRange};
193    ///
194    /// let mut partitioning1 = AlphabetPartition::empty();
195    /// partitioning1.insert_unchecked(CharRange::new('a', 'z'));
196    ///
197    /// let mut partitioning2 = AlphabetPartition::empty();
198    /// partitioning2.insert_unchecked(CharRange::new('b', 'c'));
199    ///
200    /// let refined_partitioning = partitioning1.refine(&partitioning2);
201    /// let mut iter = refined_partitioning.iter();
202    /// assert_eq!(iter.next(), Some(&CharRange::new('a', 'a')));
203    /// assert_eq!(iter.next(), Some(&CharRange::new('b', 'c')));
204    /// assert_eq!(iter.next(), Some(&CharRange::new('d', 'z')));
205    /// ```
206    pub fn refine(&self, other: &Self) -> Self {
207        let map = self.map.refine(&other.map, |_, _| ());
208        Self { map }
209    }
210
211    /// Returns an iterator over the character ranges in the partitioning.
212    ///
213    /// The iterator yields each [`CharRange`] in the partitioning, in sorted order.
214    ///
215    /// # Examples
216    ///
217    /// ```
218    /// use smt_str::alphabet::{partition::AlphabetPartition, CharRange};
219    ///
220    /// let mut partitioning = AlphabetPartition::empty();
221    /// partitioning.insert_unchecked(CharRange::new('a', 'b'));
222    /// partitioning.insert_unchecked(CharRange::new('x', 'z'));
223    ///
224    /// let mut iter = partitioning.iter();
225    /// assert_eq!(iter.next(), Some(&CharRange::new('a', 'b')));
226    /// assert_eq!(iter.next(), Some(&CharRange::new('x', 'z')));
227    /// assert_eq!(iter.next(), None);
228    /// ```
229    pub fn iter(&self) -> impl Iterator<Item = &CharRange> + '_ {
230        self.map.iter().map(|(r, _)| r)
231    }
232
233    /// Returns `true` if the partitioning is valid, i.e., if no two character ranges overlap.
234    /// This property holds as long as `insert_unchecked` is not used to insert overlapping ranges.
235    ///
236    /// # Example
237    /// ```
238    /// use smt_str::alphabet::{partition::AlphabetPartition, CharRange};
239    /// let mut p = AlphabetPartition::empty();
240    /// p.insert_unchecked(CharRange::new('a', 'f'));
241    /// p.insert_unchecked(CharRange::new('g', 'z'));
242    /// assert!(p.valid());
243    /// p.insert_unchecked(CharRange::new('e', 'h'));
244    /// assert!(!p.valid());
245    ///
246    pub fn valid(&self) -> bool {
247        self.map.valid()
248    }
249}
250
251impl Display for AlphabetPartition {
252    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
253        write!(f, "{{")?;
254        for (i, r) in self.iter().enumerate() {
255            write!(f, "{}", r)?;
256            if i < self.len() - 1 {
257                write!(f, ", ")?;
258            }
259        }
260        write!(f, "}}")
261    }
262}
263
264/// A partitioning of the SMT-LIB alphabet into disjoint character ranges, each associated with a value of type `T`.
265///
266/// See the module-level documentation for details.
267///
268/// /// # Example
269/// ```
270/// use smt_str::alphabet::{partition::AlphabetPartitionMap, CharRange};
271///
272/// let mut p = AlphabetPartitionMap::empty();
273/// p.insert(CharRange::new('a', 'f'), 1).unwrap();
274/// p.insert(CharRange::new('g', 'z'), 2).unwrap();
275///
276/// assert_eq!(p.len(), 2);
277/// assert_eq!(p.get(&CharRange::new('a', 'f')), Some(&1));
278/// ```
279#[derive(Clone, Default, Debug)]
280pub struct AlphabetPartitionMap<T: Clone> {
281    /// The character ranges in the partitioning and the associated values.
282    /// The partitions are ordered in a BTreeMap by the start and end of the character range.
283    parts: BTreeMap<CharRange, T>,
284}
285
286impl<T: Clone> AlphabetPartitionMap<T> {
287    /// Creates a new, empty partitioning.
288    ///
289    /// The resulting partitioning contains no character ranges.
290    ///
291    /// # Example
292    /// ```
293    /// use smt_str::alphabet::partition::AlphabetPartitionMap;
294    /// let p: AlphabetPartitionMap<i32> = AlphabetPartitionMap::empty();
295    /// assert!(p.is_empty());
296    /// assert_eq!(p.len(), 0);
297    /// ```
298    pub fn empty() -> Self {
299        Self {
300            parts: BTreeMap::new(),
301        }
302    }
303
304    /// Creates a partitioning map containing a single character range with the given associated value.
305    ///
306    /// # Example
307    /// ```
308    /// use smt_str::alphabet::{partition::AlphabetPartitionMap, CharRange};
309    ///
310    /// let range = CharRange::new('a', 'z');
311    /// let partitioning = AlphabetPartitionMap::singleton(range.clone(), 1);
312    /// assert_eq!(partitioning.len(), 1);
313    /// assert_eq!(partitioning.get(&range), Some(&1));
314    /// ```
315    pub fn singleton(r: CharRange, v: T) -> Self {
316        let parts = vec![(r, v)].into_iter().collect();
317        Self { parts }
318    }
319
320    /// Attempts to insert a character range with an associated value into the partitioning.
321    ///
322    /// This method checks whether the given range overlaps with any existing range in the partitioning.
323    /// If there is no overlap, the range is inserted and `Ok(())` is returned.
324    /// If the range overlaps with an existing partition, insertion is rejected and the conflicting range is returned in `Err`.
325    ///
326    /// This operation runs in **O(n + log n)** time, where `n` is the number of partitions.
327    /// If overlap checks are not necessary, use [`insert_unchecked`](Self::insert_unchecked) for faster insertion.
328    ///
329    /// # Example
330    /// ```
331    /// use smt_str::alphabet::{partition::AlphabetPartitionMap, CharRange};
332    ///
333    /// let mut partitioning = AlphabetPartitionMap::empty();
334    ///
335    /// let range = CharRange::new('a', 'z');
336    /// assert_eq!(partitioning.insert(range.clone(), 1), Ok(()));
337    /// assert_eq!(partitioning.get(&range), Some(&1));
338    ///
339    /// // Overlapping range cannot be inserted
340    /// assert_eq!(
341    ///     partitioning.insert(CharRange::new('m', 'p'), 1),
342    ///     Err(CharRange::new('a', 'z'))
343    /// );
344    /// ```
345    pub fn insert(&mut self, range: CharRange, v: T) -> Result<(), CharRange> {
346        match self.overlaps(range) {
347            Some((r, _)) => Err(*r),
348            None => {
349                self.insert_unchecked(range, v);
350                Ok(())
351            }
352        }
353    }
354
355    /// Inserts a character range with its associated value into the partitioning **without** checking for overlaps.
356    ///
357    /// This method assumes that the given range does not overlap with any existing partition.
358    /// If this assumption is violated, the internal becomes invalid.
359    ///
360    /// Runs in **O(log n)** time, where `n` is the number of existing partitions.
361    ///
362    /// Use this method only when you are certain that the inserted range does not conflict with existing ones.
363    /// For safe insertion with overlap checks, use [`insert`](Self::insert).
364    ///
365    /// # Example
366    /// ```
367    /// use smt_str::alphabet::{partition::AlphabetPartitionMap, CharRange};
368    ///
369    /// let mut partitioning = AlphabetPartitionMap::empty();
370    /// partitioning.insert_unchecked(CharRange::new('a','z'), 0);
371    /// assert_eq!(partitioning.get(&CharRange::new('a','z')), Some(&0));
372    ///
373    /// // Overlapping insertion is allowed, but the partitioning becomes invalid
374    /// partitioning.insert_unchecked(CharRange::new('m','p'), 1);
375    /// assert_eq!(partitioning.get(&CharRange::new('m','p')), Some(&1));
376    /// assert!(!partitioning.valid());
377    /// ```
378    pub fn insert_unchecked(&mut self, range: CharRange, v: T) {
379        self.parts.insert(range, v);
380    }
381
382    /// Returns a reference to the value associated with the given character range, if it exists.
383    ///
384    /// Only exact matches are returned.
385    /// That is, the given range must match a range in the map exactly.
386    ///
387    /// Runs in **O(log n)** time, where `n` is the number of stored partitions.
388    ///
389    /// # Example
390    /// ```
391    /// use smt_str::alphabet::{partition::AlphabetPartitionMap, CharRange};
392    ///
393    /// let mut partitioning = AlphabetPartitionMap::empty();
394    /// let range = CharRange::new('a', 'z');
395    /// partitioning.insert_unchecked(range, 42);
396    ///
397    /// assert_eq!(partitioning.get(&CharRange::new('a', 'z')), Some(&42));
398    /// assert_eq!(partitioning.get(&CharRange::new('a', 'm')), None); // no partial match
399    /// ```
400    pub fn get(&self, range: &CharRange) -> Option<&T> {
401        self.parts.get(range)
402    }
403
404    /// Removes the given range from the partitioning.
405    ///
406    /// Only removes ranges that exactly match an existing partition. Subranges or overlapping ranges will not be removed.
407    ///
408    /// Returns the associated value if the range was present, or `None` otherwise.
409    ///
410    /// Runs in **O(log n)** time, where `n` is the number of partitions.
411    ///
412    /// # Examples
413    /// ```
414    /// use smt_str::alphabet::{partition::AlphabetPartitionMap, CharRange};
415    ///
416    /// let mut partitioning = AlphabetPartitionMap::empty();
417    /// partitioning.insert_unchecked(CharRange::new('a', 'z'), 0);
418    ///
419    /// // Exact match can be removed
420    /// assert_eq!(partitioning.remove(CharRange::new('a', 'z')), Some(0));
421    ///
422    /// // Subrange does not match exactly
423    /// assert_eq!(partitioning.remove(CharRange::new('a', 'm')), None);
424    /// ```
425    pub fn remove(&mut self, range: CharRange) -> Option<T> {
426        self.parts.remove(&range)
427    }
428
429    /// Returns the number of partitions in the partitioning.
430    pub fn len(&self) -> usize {
431        self.parts.len()
432    }
433
434    /// Returns `true`precisely if the partitioning is empty.
435    pub fn is_empty(&self) -> bool {
436        self.parts.is_empty()
437    }
438
439    /// Computes the coarsest common refinement of two partitionings.
440    ///
441    /// Given two partitionings `self: P` and `other: Q`, this returns a new partitioning `R` such that every range `r ∈ R` is the intersection of:
442    ///
443    /// - a range in `P` and a range in `Q`, or
444    /// - a range in `P` and a range in the complement of `Q`, or
445    /// - a range in `Q` and a range in the complement of `P`
446    ///
447    /// In other words, `R` is the coarsest partitioning that is a common refinement of `P` and `Q`.
448    ///
449    /// The associated value for each `r ∈ R` is determined as follows:
450    ///
451    /// - If `r` is contained in a range `p ∈ P` and disjoint from all ranges in `Q`, its value is `P(p)`.
452    /// - If `r` is contained in a range `q ∈ Q` and disjoint from all ranges in `P`, its value is `Q(q)`.
453    /// - If `r` is the intersection of `p ∈ P` and `q ∈ Q`, its value is `f(P(p), Q(q))`.
454    ///
455    /// # Arguments
456    ///
457    /// * `other` — the partitioning to refine with
458    /// * `f` — a function used to merge values when ranges from both partitionings overlap
459    ///
460    /// # Example
461    ///
462    /// ```
463    /// use smt_str::alphabet::{partition::AlphabetPartitionMap, CharRange};
464    ///
465    /// let mut p1 = AlphabetPartitionMap::empty();
466    /// p1.insert_unchecked(CharRange::new('a', 'z'), 1);
467    ///
468    /// let mut p2 = AlphabetPartitionMap::empty();
469    /// p2.insert_unchecked(CharRange::new('b', 'c'), 2);
470    ///
471    /// let refined = p1.refine(&p2, |a, b| a + b);
472    ///
473    /// let mut iter = refined.iter();
474    /// assert_eq!(iter.next(), Some((&CharRange::new('a', 'a'), &1)));
475    /// assert_eq!(iter.next(), Some((&CharRange::new('b', 'c'), &3)));
476    /// assert_eq!(iter.next(), Some((&CharRange::new('d', 'z'), &1)));
477    /// assert_eq!(iter.next(), None);
478    /// ```
479    #[allow(clippy::comparison_chain)]
480    pub fn refine<F>(&self, other: &Self, f: F) -> Self
481    where
482        F: Fn(&T, &T) -> T,
483    {
484        debug_assert!(
485            self.valid(),
486            "invalid partitioning: {}",
487            self.parts
488                .keys()
489                .map(|k| k.to_string())
490                .collect::<Vec<_>>()
491                .join(", ")
492        );
493        debug_assert!(other.valid());
494        let mut refined = Self::empty();
495        let mut left_iter = self.parts.iter();
496        let mut right_iter = other.parts.iter();
497
498        let mut left = left_iter.next().map(|(l, value)| (l.start, l.end, value));
499        let mut right = right_iter.next().map(|(r, value)| (r.start, r.end, value));
500
501        while let (Some(l), Some(r)) = (left, right) {
502            let (l_start, l_end, val_l) = l;
503            let (r_start, r_end, val_r) = r;
504            if l_end < r_start {
505                // No overlap, left is before right
506                refined.insert_unchecked(CharRange::new(l_start, l_end), val_l.clone());
507                // Advance left
508                left = left_iter.next().map(|(l, v)| (l.start, l.end, v));
509            } else if r_end < l_start {
510                // No overlap, right is before left
511                refined.insert_unchecked(CharRange::new(r_start, r_end), val_r.clone());
512                // Advance right
513                right = right_iter.next().map(|(r, v)| (r.start, r.end, v))
514            } else {
515                // Overlapping ranges
516                if l_start < r_start {
517                    // (l_start < r_start < l_end < r_end) or (l_start < r_start < r_end < l_end)
518                    // Add [l_start, r_start-1], set left to [r_start, l_end]
519                    let prefix = CharRange::new(l_start, r_start.saturating_prev());
520                    refined.insert_unchecked(prefix, val_l.clone());
521                    left = Some((r_start, l_end, val_l));
522                } else if r_start < l_start {
523                    // (r_start < l_start < r_end < l_end) or (r_start < l_start < l_end < r_end)
524                    // Add [r_start, l_start-1], set right to [l_start, r_end]
525                    let prefix = CharRange::new(r_start, l_start.saturating_prev());
526                    refined.insert_unchecked(prefix, val_r.clone());
527                    right = Some((l_start, r_end, val_r));
528                } else {
529                    // l_start == r_start, one is a prefix of the other
530                    let refined_v = f(val_l, val_r);
531                    if l_end < r_end {
532                        // [l_start, l_end] is a prefix of [r_start, r_end]
533                        // Add [l_start, l_end] to the refined partitioning, advance left, and set right to [l_end+1, r_end]
534                        let prefix = CharRange::new(l_start, l_end);
535                        refined.insert_unchecked(prefix, refined_v);
536                        left = left_iter.next().map(|(l, v)| (l.start, l.end, v));
537                        right = Some((l_end.saturating_next(), r_end, val_r));
538                    } else if r_end < l_end {
539                        // [r_start, r_end] is a prefix of [l_start, l_end]
540                        // Add [r_start, r_end] to the refined partitioning, advance right, and set left to [r_end+1, l_end]
541                        let prefix = CharRange::new(r_start, r_end);
542                        refined.insert_unchecked(prefix, refined_v);
543                        right = right_iter.next().map(|(r, v)| (r.start, r.end, v));
544                        left = Some((r_end.saturating_next(), l_end, val_l));
545                    } else {
546                        // l_start == r_start && l_end == r_end
547                        // Add [l_start, l_end] to the refined partitioning, advance both
548                        refined.insert_unchecked(CharRange::new(l_start, l_end), refined_v);
549                        left = left_iter.next().map(|(l, v)| (l.start, l.end, v));
550                        right = right_iter.next().map(|(r, v)| (r.start, r.end, v))
551                    }
552                }
553            }
554        }
555
556        // Add remaining partitions
557        while let Some((start, end, v)) = left {
558            debug_assert!(right.is_none());
559            refined.insert_unchecked(CharRange::new(start, end), v.clone());
560            left = left_iter.next().map(|(l, v)| (l.start, l.end, v));
561        }
562        while let Some((start, end, v)) = right {
563            debug_assert!(left.is_none());
564            refined.insert_unchecked(CharRange::new(start, end), v.clone());
565            right = right_iter.next().map(|(r, v)| (r.start, r.end, v))
566        }
567        refined
568    }
569
570    /// Refines the partitioning with a single character range and associated value.
571    ///
572    /// This is a convenience method that creates a singleton partitioning and invokes [`refine`] with it.
573    /// The result is equivalent to refining `self` with a partitioning containing only the given range.
574    ///
575    /// See [`refine`](Self::refine) for the semantics of refinement.
576    ///
577    /// # Arguments
578    ///
579    /// * `range` — the range to refine with
580    /// * `val` — the value associated with the range
581    /// * `f` — the merge function used when `range` overlaps with an existing range
582    ///
583    /// # Example
584    ///
585    /// ```
586    /// use smt_str::alphabet::{partition::AlphabetPartitionMap, CharRange};
587    ///
588    /// let mut p = AlphabetPartitionMap::empty();
589    /// p.insert_unchecked(CharRange::new('a', 'z'), 1);
590    ///
591    /// let refined = p.refine_single(CharRange::new('b', 'c'), 2, |a, b| a + b);
592    ///
593    /// let mut iter = refined.iter();
594    /// assert_eq!(iter.next(), Some((&CharRange::new('a', 'a'), &1)));
595    /// assert_eq!(iter.next(), Some((&CharRange::new('b', 'c'), &3)));
596    /// assert_eq!(iter.next(), Some((&CharRange::new('d', 'z'), &1)));
597    /// assert_eq!(iter.next(), None);
598    /// ```
599    pub fn refine_single<F>(&self, range: CharRange, val: T, f: F) -> Self
600    where
601        F: Fn(&T, &T) -> T,
602    {
603        let temp_part = AlphabetPartitionMap::singleton(range, val);
604        self.refine(&temp_part, f)
605    }
606
607    /// Returns an iterator over the character ranges and associated values in the partitioning.
608    ///
609    /// The iterator yields pairs of [`CharRange`] and references to their associated values, in ascending order of ranges.
610    ///
611    /// # Example
612    /// ```
613    /// use smt_str::alphabet::{partition::AlphabetPartitionMap, CharRange};
614    ///
615    /// let mut p = AlphabetPartitionMap::empty();
616    /// p.insert_unchecked(CharRange::new('a', 'c'), 1);
617    /// p.insert_unchecked(CharRange::new('x', 'z'), 2);
618    ///
619    /// let mut iter = p.iter();
620    /// assert_eq!(iter.next(), Some((&CharRange::new('a', 'c'), &1)));
621    /// assert_eq!(iter.next(), Some((&CharRange::new('x', 'z'), &2)));
622    /// assert_eq!(iter.next(), None);
623    /// ```
624    pub fn iter(&self) -> impl Iterator<Item = (&CharRange, &T)> + '_ {
625        self.parts.iter()
626    }
627
628    /// Returns an iterator over the character ranges and mutable references to their associated values.
629    ///
630    /// The iterator yields pairs of [`CharRange`] and mutable references to their associated values,
631    /// in ascending order of ranges. This allows modifying the values in-place.
632    ///
633    /// # Example
634    /// ```
635    /// use smt_str::alphabet::{partition::AlphabetPartitionMap, CharRange};
636    ///
637    /// let mut p = AlphabetPartitionMap::empty();
638    /// p.insert_unchecked(CharRange::new('a', 'c'), 1);
639    /// p.insert_unchecked(CharRange::new('x', 'z'), 2);
640    ///
641    /// for (_, value) in p.iter_mut() {
642    ///     *value += 1;
643    /// }
644    ///
645    /// let mut iter = p.iter();
646    /// assert_eq!(iter.next(), Some((&CharRange::new('a', 'c'), &2)));
647    /// assert_eq!(iter.next(), Some((&CharRange::new('x', 'z'), &3)));
648    /// assert_eq!(iter.next(), None);
649    /// ```
650    pub fn iter_mut(&mut self) -> impl Iterator<Item = (&CharRange, &mut T)> + '_ {
651        self.parts.iter_mut()
652    }
653
654    /// Checks whether the given character range overlaps with any existing partition in the map.
655    ///
656    /// Returns the first overlapping `(CharRange, value)` pair if any overlap exists; otherwise returns `None`.
657    ///
658    /// This method performs a linear scan over all ranges and runs in `O(n)` time, where `n` is the number of partitions.
659    /// TODO: Could be optimized to `O(log n)` using binary search.
660    ///
661    /// # Arguments
662    /// - `range`: The [`CharRange`] to test for overlap.
663    ///
664    /// # Example
665    /// ```
666    /// use smt_str::alphabet::{partition::AlphabetPartitionMap, CharRange};
667    ///
668    /// let mut p = AlphabetPartitionMap::empty();
669    /// p.insert_unchecked(CharRange::new('a', 'z'), 1);
670    ///
671    /// assert!(p.overlaps(CharRange::new('m', 'p')).is_some());
672    /// assert!(p.overlaps(CharRange::new('0', '9')).is_none());
673    /// ```
674    pub fn overlaps(&self, range: CharRange) -> Option<(&CharRange, &T)> {
675        self.parts
676            .iter()
677            .find(|(r, _)| !r.intersect(&range).is_empty())
678    }
679
680    /// Returns `true` if the partitioning is valid, i.e., if no two character ranges overlap.
681    /// This property holds as long as `insert_unchecked` is not used to insert overlapping ranges.
682    ///
683    /// This method checks that for every pair of consecutive ranges `(r1, r2)`, the end of `r1` is strictly less than the start of `r2`.
684    /// This ensures that the partitioning forms a set of non-overlapping ranges.
685    ///
686    /// Runs in `O(n)` time, where `n` is the number of partitions.
687    ///
688    /// # Example
689    /// ```
690    /// use smt_str::alphabet::{partition::AlphabetPartitionMap, CharRange};
691    ///
692    /// let mut p = AlphabetPartitionMap::empty();
693    /// p.insert_unchecked(CharRange::new('a', 'f'), 1);
694    /// p.insert_unchecked(CharRange::new('g', 'z'), 2);
695    /// assert!(p.valid());
696    ///
697    /// // Overlapping range leads to invalid partitioning
698    /// p.insert_unchecked(CharRange::new('e', 'h'), 3);
699    /// assert!(!p.valid());
700    /// ```
701    pub fn valid(&self) -> bool {
702        self.parts
703            .keys()
704            .zip(self.parts.keys().skip(1))
705            .all(|(r1, r2)| r1.end < r2.start)
706    }
707}
708
709impl<T: Clone> IntoIterator for AlphabetPartitionMap<T> {
710    type Item = (CharRange, T);
711
712    type IntoIter = btree_map::IntoIter<CharRange, T>;
713
714    fn into_iter(self) -> Self::IntoIter {
715        self.parts.into_iter()
716    }
717}
718
719impl<T: Display + Clone> Display for AlphabetPartitionMap<T> {
720    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
721        write!(f, "{{")?;
722        for (i, (r, v)) in self.iter().enumerate() {
723            write!(f, "{}:{}", r, v)?;
724            if i < self.len() - 1 {
725                write!(f, ", ")?;
726            }
727        }
728        write!(f, "}}")
729    }
730}
731
732#[cfg(test)]
733mod test {
734
735    use super::*;
736
737    #[test]
738    fn refine_al_subsumed() {
739        let r1 = CharRange::new(2u32, 5u32);
740        let r2 = CharRange::new(3u32, 6u32);
741        let r3 = CharRange::new(1u32, 4u32);
742        let mut part = AlphabetPartition::empty();
743        part = part.refine(&AlphabetPartition::singleton(r1));
744
745        part = part.refine(&AlphabetPartition::singleton(r2));
746
747        part = part.refine(&AlphabetPartition::singleton(r3));
748
749        let mut iter = part.iter();
750        assert_eq!(iter.next(), Some(&CharRange::new(1u32, 1u32)));
751        assert_eq!(iter.next(), Some(&CharRange::new(2u32, 2u32)));
752        assert_eq!(iter.next(), Some(&CharRange::new(3u32, 4u32)));
753        assert_eq!(iter.next(), Some(&CharRange::new(5u32, 5u32)));
754        assert_eq!(iter.next(), Some(&CharRange::new(6u32, 6u32)));
755        assert_eq!(iter.next(), None);
756    }
757
758    #[test]
759    fn test_empty_partitioning() {
760        let partitioning: AlphabetPartitionMap<i32> = AlphabetPartitionMap::empty();
761        assert!(partitioning.parts.is_empty());
762    }
763
764    #[test]
765    fn test_singleton_partitioning() {
766        let range = CharRange::new('a', 'z');
767        let partitioning = AlphabetPartitionMap::singleton(range, 1);
768        assert_eq!(partitioning.get(&range), Some(&1));
769    }
770
771    #[test]
772    fn test_insert_non_overlapping() {
773        let mut partitioning = AlphabetPartitionMap::empty();
774
775        // Insert non-overlapping ranges
776        let range1 = CharRange::new('a', 'f');
777        let range2 = CharRange::new('g', 'z');
778
779        assert_eq!(partitioning.insert(range1, 1), Ok(()));
780        assert_eq!(partitioning.insert(range2, 2), Ok(()));
781        assert_eq!(partitioning.get(&range1), Some(&1));
782        assert_eq!(partitioning.get(&range2), Some(&2));
783    }
784
785    #[test]
786    fn test_insert_overlapping() {
787        let mut partitioning = AlphabetPartitionMap::empty();
788
789        // Insert initial range
790        let range1 = CharRange::new('a', 'm');
791        let overlapping_range = CharRange::new('g', 'z');
792
793        assert_eq!(partitioning.insert(range1, 1), Ok(()));
794
795        // Insert overlapping range, expect an error
796        assert_eq!(partitioning.insert(overlapping_range, 2), Err(range1));
797    }
798
799    #[test]
800    fn test_remove_partition() {
801        let mut partitioning = AlphabetPartitionMap::empty();
802
803        let range = CharRange::new('a', 'z');
804        partitioning.insert_unchecked(range, 1);
805        assert_eq!(partitioning.get(&range), Some(&1));
806
807        // Now remove the range and check if it's gone
808        partitioning.remove(range);
809        assert_eq!(partitioning.get(&range), None);
810    }
811
812    #[test]
813    fn test_refine_fully_overlapping() {
814        let mut partitioning1 = AlphabetPartitionMap::empty();
815        partitioning1.insert_unchecked(CharRange::new('a', 'z'), 1);
816
817        let mut partitioning2 = AlphabetPartitionMap::empty();
818        partitioning2.insert_unchecked(CharRange::new('a', 'z'), 2);
819
820        // Fully overlapping, so the function should combine the values (1 + 2).
821        let refined_partitioning = partitioning1.refine(&partitioning2, |v1, v2| v1 + v2);
822
823        let mut iter = refined_partitioning.iter();
824        assert_eq!(iter.next(), Some((&CharRange::new('a', 'z'), &3))); // 1 + 2 = 3
825        assert_eq!(iter.next(), None);
826    }
827
828    #[test]
829    fn test_refine_partial_overlap() {
830        let mut partitioning1 = AlphabetPartitionMap::empty();
831        partitioning1.insert_unchecked(CharRange::new('a', 'm'), 1);
832
833        let mut partitioning2 = AlphabetPartitionMap::empty();
834        partitioning2.insert_unchecked(CharRange::new('g', 'z'), 2);
835
836        // Partial overlap: 'g' to 'm' is overlapping, other parts are non-overlapping.
837        let refined_partitioning = partitioning1.refine(&partitioning2, |v1, v2| v1 + v2);
838
839        let mut iter = refined_partitioning.iter();
840        assert_eq!(iter.next(), Some((&CharRange::new('a', 'f'), &1))); // non-overlapping from partitioning1
841        assert_eq!(iter.next(), Some((&CharRange::new('g', 'm'), &3))); // overlapping part (1 + 2)
842        assert_eq!(iter.next(), Some((&CharRange::new('n', 'z'), &2))); // non-overlapping from partitioning2
843        assert_eq!(iter.next(), None);
844    }
845
846    #[test]
847    fn test_refine_complex_overlaps() {
848        let mut part1 = AlphabetPartitionMap::empty();
849        part1.insert_unchecked(CharRange::new('a', 'e'), 1);
850        part1.insert_unchecked(CharRange::new('f', 'j'), 3);
851
852        let mut part2 = AlphabetPartitionMap::empty();
853        part2.insert_unchecked(CharRange::new('d', 'g'), 2);
854        part2.insert_unchecked(CharRange::new('h', 'k'), 4);
855
856        // Overlapping in multiple segments, combining values accordingly.
857        let refined_partitioning = part1.refine(&part2, |v1, v2| v1 * v2);
858
859        let mut iter = refined_partitioning.iter();
860        assert_eq!(iter.next(), Some((&CharRange::new('a', 'c'), &1))); // non-overlapping part from partitioning1
861        assert_eq!(iter.next(), Some((&CharRange::new('d', 'e'), &2))); // overlap: 1 * 2 = 2
862        assert_eq!(iter.next(), Some((&CharRange::new('f', 'g'), &6))); // overlap: 3 * 2 = 6
863        assert_eq!(iter.next(), Some((&CharRange::new('h', 'j'), &12))); // overlap: 3 * 4 = 12
864        assert_eq!(iter.next(), Some((&CharRange::new('k', 'k'), &4))); // non-overlapping part from partitioning2
865        assert_eq!(iter.next(), None);
866    }
867
868    #[test]
869    fn test_refine_adjacent_ranges() {
870        let mut partitioning1 = AlphabetPartitionMap::empty();
871        partitioning1.insert_unchecked(CharRange::new('a', 'f'), 1);
872
873        let mut partitioning2 = AlphabetPartitionMap::empty();
874        partitioning2.insert_unchecked(CharRange::new('g', 'z'), 2);
875
876        // Adjacent ranges, no overlap
877        let refined_partitioning = partitioning1.refine(&partitioning2, |v1, v2| v1 + v2);
878
879        let mut iter = refined_partitioning.iter();
880        assert_eq!(iter.next(), Some((&CharRange::new('a', 'f'), &1))); // partitioning1
881        assert_eq!(iter.next(), Some((&CharRange::new('g', 'z'), &2))); // partitioning2
882        assert_eq!(iter.next(), None);
883    }
884
885    #[test]
886    fn test_refine_with_no_overlap() {
887        let mut partitioning1 = AlphabetPartitionMap::empty();
888        partitioning1.insert_unchecked(CharRange::new('a', 'c'), 1);
889
890        let mut partitioning2 = AlphabetPartitionMap::empty();
891        partitioning2.insert_unchecked(CharRange::new('x', 'z'), 2);
892
893        // No overlap at all
894        let refined_partitioning = partitioning1.refine(&partitioning2, |v1, v2| v1 + v2);
895
896        let mut iter = refined_partitioning.iter();
897        assert_eq!(iter.next(), Some((&CharRange::new('a', 'c'), &1))); // partitioning1
898        assert_eq!(iter.next(), Some((&CharRange::new('x', 'z'), &2))); // partitioning2
899        assert_eq!(iter.next(), None);
900    }
901}