itertools/array_impl.rs
1use crate::Itertools;
2use std::iter::Fuse;
3
4/// An iterator over all contiguous windows of the input iterator,
5/// producing arrays of a specific size.
6///
7/// See [`.array_windows()`](crate::Itertools::array_windows) for more
8/// information.
9#[derive(Debug, Clone)]
10pub struct ArrayWindows<I, const N: usize>
11where
12 I: Iterator + Sized,
13 I::Item: Clone,
14{
15 iter: Fuse<I>,
16 inner: Option<ArrayWindowsInner<I::Item, N>>,
17}
18
19#[derive(Debug, Clone)]
20struct ArrayWindowsInner<T: Clone, const N: usize> {
21 // `window` stores the `N` items delivered in the most
22 // recent output window. It is stored in the form of a ring
23 // buffer, with `window_start` identifying the element
24 // that logically comes first.
25 window: [T; N],
26 window_start: usize,
27}
28
29impl<T: Clone, const N: usize> ArrayWindowsInner<T, N> {
30 /// Replace the least recent item in `window` with a new
31 /// item.
32 fn add_to_buffer(&mut self, item: T) {
33 if N > 0 {
34 self.window[self.window_start] = item;
35 self.window_start = (self.window_start + 1) % N;
36 }
37 }
38
39 /// Construct an array window to return.
40 fn make_window(&self) -> [T; N] {
41 std::array::from_fn(|i| self.window[(i + self.window_start) % N].clone())
42 }
43}
44
45impl<I, const N: usize> Iterator for ArrayWindows<I, N>
46where
47 I: Iterator + Sized,
48 I::Item: Clone,
49{
50 type Item = [I::Item; N];
51
52 fn next(&mut self) -> Option<[I::Item; N]> {
53 match &mut self.inner {
54 // Initialisation code, when next() is called for the first time
55 None => match self.iter.next_array() {
56 None => {
57 // The input iterator was completely empty
58 None
59 }
60 Some(buf) => {
61 let inner = ArrayWindowsInner {
62 window: buf,
63 window_start: 0,
64 };
65 let window = inner.make_window();
66 self.inner = Some(inner);
67 Some(window)
68 }
69 },
70 Some(inner) => match self.iter.next() {
71 Some(item) => {
72 inner.add_to_buffer(item);
73 Some(inner.make_window())
74 }
75 None => None,
76 },
77 }
78 }
79}
80
81pub fn array_windows<I, const N: usize>(iter: I) -> ArrayWindows<I, N>
82where
83 I: Iterator + Sized,
84 I::Item: Clone,
85{
86 ArrayWindows {
87 iter: iter.fuse(),
88 inner: None,
89 }
90}
91
92/// An iterator over all windows, wrapping back to the first elements when the
93/// window would otherwise exceed the length of the iterator, producing arrays
94/// of a specific size.
95///
96/// See [`.circular_array_windows()`](crate::Itertools::circular_array_windows)
97/// for more information.
98#[derive(Debug, Clone)]
99pub struct CircularArrayWindows<I, const N: usize>
100where
101 I: Iterator + Sized,
102 I::Item: Clone,
103{
104 iter: Fuse<I>,
105 inner: Option<CircularArrayWindowsInner<I::Item, N>>,
106}
107
108#[derive(Debug, Clone)]
109struct CircularArrayWindowsInner<T: Clone, const N: usize> {
110 // `prefix` stores the first `N` items output from this iterator.
111 // If the input contained fewer than `N` items, then it is filled
112 // with clones of the previous items in a cycle.
113 //
114 // `prefix_pos` tracks the number of items that have been _used_
115 // from `prefix`. It begins counting up from 0 once the input runs
116 // out. (So in the case where the input iterator is shorter than
117 // `N`, it will begin counting up before `prefix` has even been
118 // populated during setup.)
119 prefix: [T; N],
120 prefix_pos: usize,
121
122 // For delivering the output arrays, we reuse `ArrayWindowsInner`
123 // unchanged.
124 arraywin: ArrayWindowsInner<T, N>,
125}
126
127impl<I, const N: usize> Iterator for CircularArrayWindows<I, N>
128where
129 I: Iterator + Sized,
130 I::Item: Clone,
131{
132 type Item = [I::Item; N];
133
134 fn next(&mut self) -> Option<[I::Item; N]> {
135 match &mut self.inner {
136 // Initialisation code, when next() is called for the first time
137 None => match self.iter.next() {
138 None => {
139 // The input iterator was completely empty
140 None
141 }
142 Some(first) => {
143 // We have at least one item, so we can definitely
144 // populate `prefix` (even if we have to make N
145 // copies of this element).
146
147 // Construct [Option<T>; N] and convert to [T; N]
148 // once it's full. TODO: can this be improved?
149 let mut items = std::array::from_fn(|_| None);
150 let mut prefix_pos = 0;
151 if N > 0 {
152 // The first item stored is the one passed to
153 // us from our caller.
154 items[0] = Some(first);
155 }
156 for i in 1..N {
157 // Populate the remaining slots in `items`
158 // from the input iterator.
159 items[i] = self.iter.next();
160 if items[i].is_none() {
161 // If the input iterator runs out early,
162 // populate the rest of `items` by
163 // recycling from the beginning, and set
164 // `prefix_pos` to indicate that we have
165 // already consumed those items.
166 for j in i..N {
167 items[j] = items[j - i].clone();
168 }
169 prefix_pos = N - i;
170 break;
171 }
172 }
173 let items = items.map(Option::unwrap);
174
175 let inner = CircularArrayWindowsInner {
176 prefix: items.clone(),
177 prefix_pos,
178 arraywin: ArrayWindowsInner {
179 window: items,
180 window_start: 0,
181 },
182 };
183
184 let window = inner.arraywin.make_window();
185 self.inner = Some(inner);
186 Some(window)
187 }
188 },
189 Some(inner) => {
190 // Normal case. Read the next item in the logical
191 // input sequence (consisting of the contents of the
192 // input iterator followed by N-1 items recycling from
193 // the beginning), and add it to the ring buffer.
194 let item = if let Some(item) = self.iter.next() {
195 // Read from the input iterator.
196 item
197 } else {
198 assert!(N == 0 || inner.prefix_pos < N);
199 if N == 0 || inner.prefix_pos + 1 == N {
200 // The input iterator has run out, and we've
201 // emitted as many windows as we read items,
202 // so we've finished.
203 return None;
204 }
205 let item = inner.prefix[inner.prefix_pos].clone();
206 inner.prefix_pos += 1;
207 item
208 };
209
210 inner.arraywin.add_to_buffer(item);
211 Some(inner.arraywin.make_window())
212 }
213 }
214 }
215}
216
217pub fn circular_array_windows<I, const N: usize>(iter: I) -> CircularArrayWindows<I, N>
218where
219 I: Iterator + Sized,
220 I::Item: Clone,
221{
222 CircularArrayWindows {
223 iter: iter.fuse(),
224 inner: None,
225 }
226}