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}