Skip to main content

int_interval_stack/
stack_window.rs

1use std::num::NonZeroUsize;
2
3use int_interval::traits::{COStartLenConstruct, IntCO, IntPrimitive};
4use rayon::iter::{IndexedParallelIterator, IntoParallelIterator, ParallelIterator};
5
6use crate::{HeightRun, IntCOStack};
7
8#[derive(Debug, Clone, Copy)]
9pub struct StackWindow<'a, I>
10where
11    I: IntCO,
12{
13    pub(crate) stack: &'a IntCOStack<I>,
14    pub(crate) interval: I,
15    /// First change point strictly inside the window after `interval.start()`.
16    pub(crate) point_start: usize,
17    /// First change point at or after `interval.end_excl()`.
18    pub(crate) point_end: usize,
19    /// Stack height at `interval.start()`.
20    pub(crate) height_at_start: usize,
21}
22
23impl<'a, I> StackWindow<'a, I>
24where
25    I: IntCO,
26{
27    pub(crate) fn new(stack: &'a IntCOStack<I>, interval: I) -> Self {
28        let points = stack.change_points();
29
30        let window_start = interval.start();
31        let window_end = interval.end_excl();
32
33        let point_start = points.partition_point(|point| point.at <= window_start);
34        let point_end =
35            point_start + points[point_start..].partition_point(|point| point.at < window_end);
36
37        let height_at_start = point_start
38            .checked_sub(1)
39            .map_or(0, |index| points[index].height_after);
40
41        Self {
42            stack,
43            interval,
44            point_start,
45            point_end,
46            height_at_start,
47        }
48    }
49
50    #[inline]
51    pub const fn stack(&self) -> &'a IntCOStack<I> {
52        self.stack
53    }
54
55    #[inline]
56    pub const fn interval(&self) -> &I {
57        &self.interval
58    }
59
60    /// Returns the number of constant-height runs inside this window.
61    ///
62    /// A window is partitioned by every stack change point strictly inside the
63    /// window:
64    ///
65    /// ```text
66    /// [window.start, p0), [p0, p1), ..., [pn, window.end)
67    /// ```
68    ///
69    /// Therefore the number of runs is the number of interior change points plus
70    /// one. Even a window with no interior change points has one run covering the
71    /// whole window.
72    #[inline]
73    fn height_run_count(&self) -> usize {
74        self.point_end - self.point_start + 1
75    }
76
77    /// Builds the constant-height run at `run_index`.
78    ///
79    /// `run_index` is an index into the window-local run partition, not into the
80    /// global change-point array.
81    ///
82    /// For a window containing interior change points
83    /// `points[point_start..point_end]`, the run boundaries are:
84    ///
85    /// - run `0`: starts at `interval.start()`;
86    /// - run `i > 0`: starts at `points[point_start + i - 1].at`;
87    /// - run `i < count - 1`: ends at `points[point_start + i].at`;
88    /// - final run: ends at `interval.end_excl()`.
89    ///
90    /// Heights follow the stack height active at each run start:
91    ///
92    /// - run `0` uses `height_at_start`;
93    /// - run `i > 0` uses the height after the preceding interior change point.
94    #[inline]
95    fn height_run_at(&self, run_index: usize) -> HeightRun<I> {
96        debug_assert!(run_index < self.height_run_count());
97
98        let points = self.stack.change_points();
99
100        let point_index = self.point_start + run_index;
101
102        let start = if run_index == 0 {
103            self.interval.start()
104        } else {
105            points[point_index - 1].at
106        };
107
108        let end_excl = if point_index < self.point_end {
109            points[point_index].at
110        } else {
111            self.interval.end_excl()
112        };
113
114        let height = if run_index == 0 {
115            self.height_at_start
116        } else {
117            points[point_index - 1].height_after
118        };
119
120        HeightRun {
121            // SAFETY:
122            // `run_index` ranges over the partition induced by change points
123            // strictly inside this window. Therefore each produced pair is a
124            // non-empty closed-open interval.
125            interval: unsafe { I::new_unchecked(start, end_excl) },
126            height,
127        }
128    }
129}
130
131impl<I> StackWindow<'_, I>
132where
133    I: IntCO,
134{
135    /// Iterates over constant-height runs inside this window.
136    ///
137    /// Unlike `IntCOStack::iter_height_segments`, this includes zero-height
138    /// runs because window-level mappings may assign a non-zero value to
139    /// height zero.
140    #[inline]
141    pub fn iter_height_runs(
142        &self,
143    ) -> impl DoubleEndedIterator<Item = HeightRun<I>> + ExactSizeIterator {
144        (0..self.height_run_count()).map(move |run_index| self.height_run_at(run_index))
145    }
146}
147
148impl<I> StackWindow<'_, I>
149where
150    I: IntCO + Send + Sync,
151{
152    /// Iterates in parallel over constant-height runs inside this window.
153    ///
154    /// The run range is represented as an indexed integer range, so Rayon can
155    /// split the work directly. This is mainly useful when the per-run mapping
156    /// is expensive or the window contains many height changes.
157    #[inline]
158    pub fn par_iter_height_runs(&self) -> impl IndexedParallelIterator<Item = HeightRun<I>> {
159        (0..self.height_run_count())
160            .into_par_iter()
161            .map(move |run_index| self.height_run_at(run_index))
162    }
163}
164
165// ---------------------------------------------------------------------------
166// WindowIter – sliding-window iterator
167// ---------------------------------------------------------------------------
168
169/// Iterator over sliding windows that uses incremental index advancement for
170/// O(1) amortized forward steps.
171///
172/// Backward iteration falls back to a binary search per step.
173#[derive(Debug, Clone)]
174pub(crate) struct WindowIter<'a, I>
175where
176    I: IntCO,
177{
178    pub(crate) stack: &'a IntCOStack<I>,
179    /// Iteration domain start, stored for `DoubleEndedIterator::next_back`.
180    pub(crate) from: I::CoordType,
181    /// Current window `[start, start + len)`.
182    pub(crate) interval: I,
183    /// First change point strictly inside the window after `interval.start()`.
184    pub(crate) point_start: usize,
185    /// First change point at or after `interval.end_excl()`.
186    pub(crate) point_end: usize,
187    /// Stack height at `interval.start()`.
188    pub(crate) height_at_start: usize,
189    /// Number of windows remaining, including the current one.
190    pub(crate) remaining: usize,
191    /// Total window count (constant).
192    pub(crate) total_count: usize,
193    /// Number of windows already taken from the back via `next_back`.
194    pub(crate) consumed_back: usize,
195}
196
197impl<'a, I> WindowIter<'a, I>
198where
199    I: IntCO + COStartLenConstruct + Copy,
200{
201    /// Positions the iterator at the first window `[from, from + len)`.
202    ///
203    /// Uses a binary search once to locate the initial change-point range.
204    pub(crate) fn new(
205        stack: &'a IntCOStack<I>,
206        from: I::CoordType,
207        len: I::MeasureType,
208        count: NonZeroUsize,
209    ) -> Self {
210        let interval = I::checked_from_start_len(from, len)
211            .expect("validated window count guarantees a representable first window");
212
213        let sw = StackWindow::new(stack, interval);
214
215        Self {
216            stack,
217            from,
218            interval,
219            point_start: sw.point_start,
220            point_end: sw.point_end,
221            height_at_start: sw.height_at_start,
222            remaining: count.get(),
223            total_count: count.get(),
224            consumed_back: 0,
225        }
226    }
227
228    /// Slides the window forward by one coordinate unit.
229    ///
230    /// Advances `point_start` and `point_end` past any change points that fall
231    /// on or before the new window boundaries.
232    ///
233    /// # Panics
234    ///
235    /// Panics on overflow if `remaining` is zero (debug-only via `debug_assert`).
236    #[inline]
237    fn advance(&mut self) {
238        debug_assert!(self.remaining > 0);
239
240        let new_start = self
241            .interval
242            .start()
243            .checked_next()
244            .expect("remaining > 0 guarantees the next start coordinate fits");
245        let new_end = self
246            .interval
247            .end_excl()
248            .checked_next()
249            .expect("remaining > 0 guarantees the next end coordinate fits");
250
251        // SAFETY: `remaining > 0` implies the window still fits within the
252        // iteration domain `[from, to)`, so `new_start < new_end` and the
253        // interval is well-formed.
254        let new_interval = unsafe { I::new_unchecked(new_start, new_end) };
255
256        let points = self.stack.change_points();
257
258        // Advance point_start past change points at or before the new start.
259        while self.point_start < points.len() && points[self.point_start].at <= new_start {
260            self.height_at_start = points[self.point_start].height_after;
261            self.point_start += 1;
262        }
263
264        // Advance point_end past change points strictly before the new end.
265        while self.point_end < points.len() && points[self.point_end].at < new_end {
266            self.point_end += 1;
267        }
268
269        self.interval = new_interval;
270        self.remaining -= 1;
271    }
272}
273
274impl<'a, I> Iterator for WindowIter<'a, I>
275where
276    I: IntCO + COStartLenConstruct + Copy,
277{
278    type Item = StackWindow<'a, I>;
279
280    #[inline]
281    fn next(&mut self) -> Option<Self::Item> {
282        if self.remaining == 0 {
283            return None;
284        }
285        let window = StackWindow {
286            stack: self.stack,
287            interval: self.interval,
288            point_start: self.point_start,
289            point_end: self.point_end,
290            height_at_start: self.height_at_start,
291        };
292        self.advance();
293        Some(window)
294    }
295
296    #[inline]
297    fn size_hint(&self) -> (usize, Option<usize>) {
298        (self.remaining, Some(self.remaining))
299    }
300}
301
302impl<'a, I> ExactSizeIterator for WindowIter<'a, I>
303where
304    I: IntCO + COStartLenConstruct + Copy,
305{
306    #[inline]
307    fn len(&self) -> usize {
308        self.remaining
309    }
310}
311
312#[cfg(test)]
313mod tests_for_window_iter;
314
315#[cfg(test)]
316mod tests_for_new;
317
318#[cfg(test)]
319mod tests_for_height_run_at;
320
321#[cfg(test)]
322mod tests_for_iter_height_runs;