Skip to main content

int_interval_stack/
stack_window.rs

1use int_interval::traits::IntCO;
2use rayon::iter::{IndexedParallelIterator, IntoParallelIterator, ParallelIterator};
3
4use crate::{HeightRun, IntCOStack};
5
6#[derive(Debug, Clone, Copy)]
7pub struct StackWindow<'a, I>
8where
9    I: IntCO,
10{
11    stack: &'a IntCOStack<I>,
12    interval: I,
13    /// First change point strictly inside the window after `interval.start()`.
14    point_start: usize,
15    /// First change point at or after `interval.end_excl()`.
16    point_end: usize,
17    /// Stack height at `interval.start()`.
18    height_at_start: usize,
19}
20
21impl<'a, I> StackWindow<'a, I>
22where
23    I: IntCO,
24{
25    pub(crate) fn new(stack: &'a IntCOStack<I>, interval: I) -> Self {
26        let points = stack.change_points();
27
28        let window_start = interval.start();
29        let window_end = interval.end_excl();
30
31        let point_start = points.partition_point(|point| point.at <= window_start);
32        let point_end =
33            point_start + points[point_start..].partition_point(|point| point.at < window_end);
34
35        let height_at_start = point_start
36            .checked_sub(1)
37            .map_or(0, |index| points[index].height_after);
38
39        Self {
40            stack,
41            interval,
42            point_start,
43            point_end,
44            height_at_start,
45        }
46    }
47
48    #[inline]
49    pub const fn stack(&self) -> &'a IntCOStack<I> {
50        self.stack
51    }
52
53    #[inline]
54    pub const fn interval(&self) -> &I {
55        &self.interval
56    }
57
58    /// Returns the number of constant-height runs inside this window.
59    ///
60    /// A window is partitioned by every stack change point strictly inside the
61    /// window:
62    ///
63    /// ```text
64    /// [window.start, p0), [p0, p1), ..., [pn, window.end)
65    /// ```
66    ///
67    /// Therefore the number of runs is the number of interior change points plus
68    /// one. Even a window with no interior change points has one run covering the
69    /// whole window.
70    #[inline]
71    fn height_run_count(&self) -> usize {
72        self.point_end - self.point_start + 1
73    }
74
75    /// Builds the constant-height run at `run_index`.
76    ///
77    /// `run_index` is an index into the window-local run partition, not into the
78    /// global change-point array.
79    ///
80    /// For a window containing interior change points
81    /// `points[point_start..point_end]`, the run boundaries are:
82    ///
83    /// - run `0`: starts at `interval.start()`;
84    /// - run `i > 0`: starts at `points[point_start + i - 1].at`;
85    /// - run `i < count - 1`: ends at `points[point_start + i].at`;
86    /// - final run: ends at `interval.end_excl()`.
87    ///
88    /// Heights follow the stack height active at each run start:
89    ///
90    /// - run `0` uses `height_at_start`;
91    /// - run `i > 0` uses the height after the preceding interior change point.
92    #[inline]
93    fn height_run_at(&self, run_index: usize) -> HeightRun<I> {
94        debug_assert!(run_index < self.height_run_count());
95
96        let points = self.stack.change_points();
97
98        let point_index = self.point_start + run_index;
99
100        let start = if run_index == 0 {
101            self.interval.start()
102        } else {
103            points[point_index - 1].at
104        };
105
106        let end_excl = if point_index < self.point_end {
107            points[point_index].at
108        } else {
109            self.interval.end_excl()
110        };
111
112        let height = if run_index == 0 {
113            self.height_at_start
114        } else {
115            points[point_index - 1].height_after
116        };
117
118        HeightRun {
119            // SAFETY:
120            // `run_index` ranges over the partition induced by change points
121            // strictly inside this window. Therefore each produced pair is a
122            // non-empty closed-open interval.
123            interval: unsafe { I::new_unchecked(start, end_excl) },
124            height,
125        }
126    }
127}
128
129impl<I> StackWindow<'_, I>
130where
131    I: IntCO,
132{
133    /// Iterates over constant-height runs inside this window.
134    ///
135    /// Unlike `IntCOStack::iter_height_segments`, this includes zero-height
136    /// runs because window-level mappings may assign a non-zero value to
137    /// height zero.
138    #[inline]
139    pub fn iter_height_runs(
140        &self,
141    ) -> impl DoubleEndedIterator<Item = HeightRun<I>> + ExactSizeIterator {
142        (0..self.height_run_count()).map(move |run_index| self.height_run_at(run_index))
143    }
144}
145
146impl<I> StackWindow<'_, I>
147where
148    I: IntCO + Send + Sync,
149{
150    /// Iterates in parallel over constant-height runs inside this window.
151    ///
152    /// The run range is represented as an indexed integer range, so Rayon can
153    /// split the work directly. This is mainly useful when the per-run mapping
154    /// is expensive or the window contains many height changes.
155    #[inline]
156    pub fn par_iter_height_runs(&self) -> impl IndexedParallelIterator<Item = HeightRun<I>> {
157        (0..self.height_run_count())
158            .into_par_iter()
159            .map(move |run_index| self.height_run_at(run_index))
160    }
161}
162
163#[cfg(test)]
164mod tests_for_new;
165
166#[cfg(test)]
167mod tests_for_height_run_at;
168
169#[cfg(test)]
170mod tests_for_iter_height_runs;