Skip to main content

int_interval_stack/int_co_stack/
impls_for_iter.rs

1use either::Either;
2
3use super::*;
4
5impl<I> IntCOStack<I>
6where
7    I: IntCO,
8{
9    /// Iterates over positive-height segments by scanning canonical change points.
10    ///
11    /// This is the height-preserving fallback path used when the covered set alone
12    /// is not sufficient. Unlike `covered.iter_intervals()`, this iterator keeps
13    /// every height boundary, so adjacent covered ranges with different heights are
14    /// yielded as separate segments.
15    ///
16    /// Zero-height gaps are skipped. For each adjacent change-point pair
17    /// `(p[i], p[i + 1])`, the segment height is `p[i].height_after`.
18    #[inline]
19    fn iter_segments_from_change_points(&self) -> impl Iterator<Item = (I, usize)> + '_ {
20        self.change_points.windows(2).filter_map(|w| {
21            let start = w[0].at;
22            let end_excl = w[1].at;
23            let height = w[0].height_after;
24
25            (height != 0).then_some((
26                // SAFETY:
27                // Canonical change points are strictly increasing, so every
28                // adjacent pair forms a valid non-empty interval.
29                unsafe { I::new_unchecked(start, end_excl) },
30                height,
31            ))
32        })
33    }
34
35    /// Iterates over positive-height stack segments.
36    ///
37    /// Each item is a closed-open interval together with the stack height that
38    /// applies throughout that interval.
39    ///
40    /// If all positive-height regions have the same height, the segment intervals
41    /// are exactly the covered set intervals and the shared height is supplied
42    /// from `height_stats`. Otherwise, the height-preserving segmentation is
43    /// reconstructed from change points.
44    #[inline]
45    pub fn iter_height_segments(&self) -> impl Iterator<Item = (I, usize)> + '_ {
46        if self.height_stats.is_uniform_positive_height() {
47            let height = self.height_stats.max_height();
48
49            Either::Left(self.covered.iter_intervals().map(move |iv| (iv, height)))
50        } else {
51            Either::Right(self.iter_segments_from_change_points())
52        }
53    }
54
55    /// Iterates over positive-height stack segments whose height is at least
56    /// `min_height`.
57    ///
58    /// Each item is a closed-open interval together with the stack height that
59    /// applies throughout that interval.
60    ///
61    /// This method uses `height_stats` for cheap fast paths:
62    ///
63    /// - if `min_height` is greater than the observed maximum height, the iterator
64    ///   is empty;
65    /// - if `min_height` is less than or equal to the observed minimum positive
66    ///   height, every positive-height segment matches and `iter_height_segments`
67    ///   is reused;
68    /// - otherwise, canonical change points are scanned and filtered.
69    ///
70    /// A `min_height` of zero is treated the same as requesting all positive-height
71    /// segments, because zero-height gaps are never yielded.
72    #[inline]
73    pub fn iter_height_segments_at_least(
74        &self,
75        min_height: usize,
76    ) -> impl Iterator<Item = (I, usize)> + '_ {
77        let stack_max = self.height_stats.max_height();
78
79        if min_height > stack_max {
80            Either::Left(std::iter::empty())
81        } else if min_height <= self.height_stats.min_positive_height_or_zero() {
82            Either::Right(Either::Left(self.iter_height_segments()))
83        } else {
84            Either::Right(Either::Right(
85                self.iter_segments_from_change_points()
86                    .filter(move |&(_, h)| h >= min_height),
87            ))
88        }
89    }
90
91    /// Iterates over positive-height stack segments whose height is at most
92    /// `max_height`.
93    ///
94    /// Each item is a closed-open interval together with the stack height that
95    /// applies throughout that interval.
96    ///
97    /// This method uses `height_stats` for cheap fast paths:
98    ///
99    /// - if `max_height` is zero, the iterator is empty because zero-height gaps
100    ///   are never yielded;
101    /// - if `max_height` is smaller than the observed minimum positive height, the
102    ///   iterator is empty;
103    /// - if `max_height` is greater than or equal to the observed maximum height,
104    ///   every positive-height segment matches and `iter_height_segments` is reused;
105    /// - otherwise, canonical change points are scanned and filtered.
106    #[inline]
107    pub fn iter_height_segments_at_most(
108        &self,
109        max_height: usize,
110    ) -> impl Iterator<Item = (I, usize)> + '_ {
111        let stack_min = self.height_stats.min_positive_height_or_zero();
112
113        if max_height == 0 || max_height < stack_min {
114            Either::Left(std::iter::empty())
115        } else if max_height >= self.height_stats.max_height() {
116            Either::Right(Either::Left(self.iter_height_segments()))
117        } else {
118            Either::Right(Either::Right(
119                self.iter_segments_from_change_points()
120                    .filter(move |&(_, h)| h <= max_height),
121            ))
122        }
123    }
124
125    /// Iterates over positive-height stack segments whose height is exactly
126    /// `target_height`.
127    ///
128    /// Each item is a closed-open interval together with the stack height that
129    /// applies throughout that interval.
130    ///
131    /// This method uses `height_stats` for cheap fast paths:
132    ///
133    /// - if `target_height` is zero, the iterator is empty because zero-height gaps
134    ///   are never yielded;
135    /// - if `target_height` is outside the observed positive-height range, the
136    ///   iterator is empty;
137    /// - if all positive-height regions share the same height and `target_height`
138    ///   equals that height, the intervals are exactly the covered set intervals;
139    /// - otherwise, canonical change points are scanned and filtered.
140    #[inline]
141    pub fn iter_height_segments_exactly(
142        &self,
143        target_height: usize,
144    ) -> impl Iterator<Item = (I, usize)> + '_ {
145        let stack_min = self.height_stats.min_positive_height_or_zero();
146        let stack_max = self.height_stats.max_height();
147
148        if target_height == 0 || target_height < stack_min || target_height > stack_max {
149            Either::Left(std::iter::empty())
150        } else if self.height_stats.is_uniform_positive_height() {
151            Either::Right(Either::Left(
152                self.covered
153                    .iter_intervals()
154                    .map(move |iv| (iv, target_height)),
155            ))
156        } else {
157            Either::Right(Either::Right(
158                self.iter_segments_from_change_points()
159                    .filter(move |&(_, h)| h == target_height),
160            ))
161        }
162    }
163
164    /// Iterates over positive-height stack segments whose height is within
165    /// `min_height..=max_height`.
166    ///
167    /// Each item is a closed-open interval together with the stack height that
168    /// applies throughout that interval.
169    ///
170    /// Zero-height gaps are never yielded. Therefore, a `min_height` of zero is
171    /// treated as if it included all positive heights.
172    ///
173    /// This method uses `height_stats` for cheap fast paths:
174    ///
175    /// - if `min_height > max_height`, the iterator is empty;
176    /// - if the stack has no positive-height segments, the iterator is empty;
177    /// - if the requested range does not overlap the observed positive-height
178    ///   range, the iterator is empty;
179    /// - if the requested range covers the full observed positive-height range,
180    ///   every segment matches and `iter_height_segments` is reused;
181    /// - otherwise, canonical change points are scanned and filtered.
182    #[inline]
183    pub fn iter_height_segments_between(
184        &self,
185        min_height: usize,
186        max_height: usize,
187    ) -> impl Iterator<Item = (I, usize)> + '_ {
188        let stack_min = self.height_stats.min_positive_height_or_zero();
189        let stack_max = self.height_stats.max_height();
190        let query_min = min_height.max(1);
191
192        if min_height > max_height
193            || !self.height_stats.has_positive_height()
194            || max_height < stack_min
195            || query_min > stack_max
196        {
197            Either::Left(std::iter::empty())
198        } else if query_min <= stack_min && max_height >= stack_max {
199            Either::Right(Either::Left(self.iter_height_segments()))
200        } else {
201            Either::Right(Either::Right(
202                self.iter_segments_from_change_points()
203                    .filter(move |(_, h)| query_min <= *h && *h <= max_height),
204            ))
205        }
206    }
207
208    /// Iterates over positive-height stack segments whose height is the observed
209    /// maximum stack height.
210    ///
211    /// Each item is a closed-open interval together with the peak height that
212    /// applies throughout that interval.
213    ///
214    /// If the stack has no positive-height segments, the iterator is empty.
215    ///
216    /// This is equivalent to:
217    ///
218    /// ```text
219    /// iter_height_segments_exactly(height_stats.max_height())
220    /// ```
221    #[inline]
222    pub fn iter_peak_height_segments(&self) -> impl Iterator<Item = (I, usize)> + '_ {
223        self.iter_height_segments_exactly(self.height_stats.max_height())
224    }
225}
226
227#[cfg(test)]
228mod tests_for_iter_segments_from_change_points;
229
230#[cfg(test)]
231mod tests_for_iter_height_segments;