bellframe/
utils.rs

1use std::ops::{Bound, Range, RangeBounds};
2
3use crate::{Bell, InvalidRowError, Stage};
4use itertools::Itertools;
5
6/// Helper function to calculate the length of the longest run off the start of a given
7/// [`Iterator`]
8pub fn run_len(iter: impl IntoIterator<Item = Bell>) -> usize {
9    iter.into_iter()
10        .map(|b| b.index())
11        .tuple_windows::<(usize, usize)>()
12        // Subtraction can't over-/under-flow because i1 and i2 are both +ve
13        .take_while(|&(i1, i2)| (i1 as i8 - i2 as i8).abs() == 1)
14        .count()
15        + 1
16}
17
18/// Given some [`Bell`]s and a [`Stage`], simultaneously check for duplicate [`Bell`]s and any
19/// [`Bell`]s which are too big for the given [`Stage`].
20pub(crate) fn check_duplicate_or_out_of_stage(
21    bells: impl IntoIterator<Item = Bell>,
22    stage: Stage,
23) -> Result<(), InvalidRowError> {
24    // We check validity by keeping a checklist of which `Bell`s we've seen, and checking off
25    // each bell as we go.  PERF: use a bitmap here
26    let mut checklist = vec![false; stage.num_bells()];
27    // Loop over all the bells to check them off in the checklist.  We do not need to check for
28    // empty spaces in the checklist once we've done because (by the Pigeon Hole Principle),
29    // fitting `n` bells into `n` slots with some gaps will always require that a bell is
30    // either out of range or two bells share a slot.
31    for b in bells {
32        match checklist.get_mut(b.index()) {
33            // If the `Bell` is out of range of the checklist, it can't belong within the
34            // `Stage` of this `Row`
35            None => return Err(InvalidRowError::BellOutOfStage(b, stage)),
36            // If the `Bell` has already been seen before, then it must be a duplicate
37            Some(&mut true) => return Err(InvalidRowError::DuplicateBell(b)),
38            // If the `Bell` has not been seen before, check off the checklist entry and
39            // continue
40            Some(x) => *x = true,
41        }
42    }
43    // If none of the `Bell`s caused errors, the row must be valid
44    Ok(())
45}
46
47/// Converts any [`RangeBounds`] (i.e. `x..=y`, `..y`, `..`, etc.) into a concrete [`Range`], where
48/// unbounded min/max bounds are clamped to either `0` or `length`, respectively
49pub fn clamp_range(range: impl RangeBounds<usize>, length: usize) -> Range<usize> {
50    let range_min_inclusive = match range.start_bound() {
51        Bound::Included(v) => *v,
52        Bound::Excluded(v) => *v + 1,
53        Bound::Unbounded => 0,
54    };
55    let range_max_exclusive = match range.end_bound() {
56        Bound::Included(v) => *v + 1,
57        Bound::Excluded(v) => *v,
58        Bound::Unbounded => length,
59    };
60
61    range_min_inclusive..range_max_exclusive
62}
63
64/// Split a [`Vec`] into two segments at a given `index`
65pub fn split_vec<T>(vec: Vec<T>, index: usize) -> Option<(Vec<T>, Vec<T>)> {
66    if index > vec.len() {
67        return None; // Early return if the index is out of bounds
68    }
69    // We re-use the allocation of `vec` as the first return value, so drain the remaining elements
70    // into another `Vec` to create `other`
71    let mut left_vec = vec;
72    let right_vec = left_vec.split_off(index);
73    Some((left_vec, right_vec))
74}
75
76#[cfg(test)]
77mod tests {
78    use crate::{run_len as rl, RowBuf};
79
80    #[test]
81    fn run_len() {
82        for &(row, run_len_f) in &[("123456", 6), ("456231", 3), ("612345", 1)] {
83            assert_eq!(rl(RowBuf::parse(row).unwrap().bell_iter()), run_len_f);
84        }
85    }
86}