Skip to main content

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}