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}