Skip to main content

int_interval_stack/int_co_stack/
impls_for_iter.rs

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