Skip to main content

int_interval_stack/int_co_stack/
impls_for_windows.rs

1use std::num::NonZeroUsize;
2
3use either::Either;
4use int_interval::traits::{COStartLenConstruct, IntPrimitive};
5use rayon::iter::{IndexedParallelIterator, IntoParallelIterator, ParallelIterator};
6
7use super::*;
8use crate::stack_window::WindowIter;
9
10#[inline]
11fn window_count<I>(from: I::CoordType, to: I::CoordType, len: I::MeasureType) -> Option<usize>
12where
13    I: IntCO + COStartLenConstruct,
14    I::MeasureType: TryInto<usize>,
15{
16    if len == I::MeasureType::zero() {
17        return None;
18    }
19
20    let domain = I::try_new(from, to)?;
21    let remaining = domain.len().checked_sub(len)?;
22    let count = remaining.checked_add(I::MeasureType::one())?;
23
24    count.try_into().ok()
25}
26
27#[inline]
28fn start_at<I>(from: I::CoordType, index: usize) -> Option<I::CoordType>
29where
30    I: IntCO + COStartLenConstruct,
31{
32    if index == 0 {
33        return Some(from);
34    }
35
36    let offset = I::MeasureType::checked_from(index)?;
37
38    I::checked_from_start_len(from, offset).map(|interval| interval.end_excl())
39}
40
41#[inline]
42pub(crate) fn window_at<'a, I>(
43    stack: &'a IntCOStack<I>,
44    from: I::CoordType,
45    len: I::MeasureType,
46    index: usize,
47) -> Option<StackWindow<'a, I>>
48where
49    I: IntCO + COStartLenConstruct + Copy,
50{
51    let start = start_at::<I>(from, index)?;
52    let interval = I::checked_from_start_len(start, len)?;
53
54    Some(StackWindow::new(stack, interval))
55}
56
57impl<I> IntCOStack<I>
58where
59    I: IntCO + COStartLenConstruct + Copy,
60    I::MeasureType: TryInto<usize>,
61{
62    /// Iterates over all fixed-length windows fully contained in `[from, to)`.
63    ///
64    /// Window starts advance by one coordinate unit:
65    ///
66    /// ```text
67    /// [from,     from + len)
68    /// [from + 1, from + 1 + len)
69    /// ...
70    /// ```
71    ///
72    /// Returns an empty iterator when:
73    ///
74    /// - `from >= to`;
75    /// - `len == 0`;
76    /// - `len` is greater than the measure of `[from, to)`;
77    /// - the window count cannot be represented as `usize`.
78    #[inline]
79    pub fn iter_windows(
80        &self,
81        from: I::CoordType,
82        to: I::CoordType,
83        len: I::MeasureType,
84    ) -> impl DoubleEndedIterator<Item = StackWindow<'_, I>> + ExactSizeIterator {
85        let count = window_count::<I>(from, to, len).unwrap_or(0);
86
87        let Some(count) = NonZeroUsize::new(count) else {
88            return Either::Left(std::iter::empty());
89        };
90
91        Either::Right(WindowIter::new(self, from, len, count))
92    }
93
94    /// Iterates in parallel over all fixed-length windows fully contained in
95    /// `[from, to)`.
96    ///
97    /// The valid window-start range is represented as an indexed integer
98    /// range, allowing Rayon to split the work directly without a serial
99    /// producer or `par_bridge`.
100    #[inline]
101    pub fn par_iter_windows(
102        &self,
103        from: I::CoordType,
104        to: I::CoordType,
105        len: I::MeasureType,
106    ) -> impl IndexedParallelIterator<Item = StackWindow<'_, I>>
107    where
108        I: Send + Sync,
109    {
110        let count = window_count::<I>(from, to, len).unwrap_or(0);
111
112        (0..count).into_par_iter().map(move |index| {
113            window_at(self, from, len, index)
114                .expect("validated window index must produce a representable window")
115        })
116    }
117}
118
119// ---------------------------------------------------------------------------
120// DoubleEndedIterator – lives here because it calls the module-local
121// `window_at` helper.
122// ---------------------------------------------------------------------------
123
124impl<'a, I> DoubleEndedIterator for WindowIter<'a, I>
125where
126    I: IntCO + COStartLenConstruct + Copy,
127    I::MeasureType: TryInto<usize>,
128{
129    #[inline]
130    fn next_back(&mut self) -> Option<Self::Item> {
131        if self.remaining == 0 {
132            return None;
133        }
134
135        let back_index = self.total_count - self.consumed_back - 1;
136        self.consumed_back += 1;
137        self.remaining -= 1;
138
139        Some(
140            window_at(self.stack, self.from, self.interval.len(), back_index)
141                .expect("back index is always valid when remaining > 0"),
142        )
143    }
144}
145
146#[cfg(test)]
147mod test_support;
148
149#[cfg(test)]
150mod tests_for_window_count;
151
152#[cfg(test)]
153mod tests_for_start_at;
154
155#[cfg(test)]
156mod tests_for_window_at;
157
158#[cfg(test)]
159mod tests_for_iter_windows;