iter_chunks/
lib.rs

1use std::iter::Iterator;
2
3/// A trait that extends [`Iterator`] with `chunks` method.
4pub trait IterChunks: Sized + Iterator {
5    /// Create an iterator-liked struct that yields elements by chunk every n
6    /// elements, or fewer if the underlying iterator ends sooner.
7    ///
8    /// [`Chunks`] is not a real Iterator, but a LendingIterator, which is
9    /// currently not in std and blocked by GAT. We have to iterate with
10    /// while loop now.
11    ///
12    /// ```
13    /// use iter_chunks::IterChunks;
14    ///
15    /// let arr = [1, 1, 2, 2, 3];
16    /// let expected = [vec![1, 1], vec![2, 2], vec![3]];
17    /// let mut chunks = arr.into_iter().chunks(2);
18    /// let mut i = 0;
19    /// while let Some(chunk) = chunks.next() {
20    ///     assert_eq!(chunk.collect::<Vec<_>>(), expected[i]);
21    ///     i += 1;
22    /// }
23    /// ```
24    fn chunks(self, n: usize) -> Chunks<Self>;
25}
26
27impl<I> IterChunks for I
28where
29    I: Iterator,
30{
31    fn chunks(self, n: usize) -> Chunks<Self> {
32        assert_ne!(n, 0);
33        Chunks {
34            inner: self,
35            n,
36            end_flag: false,
37        }
38    }
39}
40
41/// An iterator-like struct that yields chunks.
42///
43/// This `struct` is created by [`chunks`] method on [`IterChunks`]. See its
44/// documentation for more.
45///
46/// [`chunks`]: IterChunks::chunks
47pub struct Chunks<I: Iterator> {
48    inner: I,
49    n: usize,
50    end_flag: bool,
51}
52
53impl<I: Iterator> Chunks<I> {
54    /// Similar to [`Iterator::next`], but not implements [`Iterator`] due to
55    /// lifetime.
56    ///
57    /// The underlying iterator implementations may choose to resume iteration
58    /// after finished, so calling `Chunks::next` may also return `Some(Chunk)`
59    /// after returning `None`.
60    #[allow(clippy::should_implement_trait)]
61    pub fn next(&mut self) -> Option<Chunk<'_, I>> {
62        if self.end_flag {
63            // The inner iterator may be resumable.
64            self.end_flag = false;
65            None
66        } else {
67            match self.inner.next() {
68                Some(v) => {
69                    let n = self.n;
70                    Some(Chunk {
71                        first: Some(v),
72                        parent: self,
73                        n: n - 1,
74                    })
75                }
76                None => None,
77            }
78        }
79    }
80
81    /// Similar to [`Iterator::for_each`].
82    ///
83    /// ```
84    /// use iter_chunks::IterChunks;
85    ///
86    /// let arr = [1, 4, 2, 3, 5];
87    /// arr.into_iter().chunks(2).for_each(|chunk| {
88    ///     assert_eq!(chunk.sum::<i32>(), 5);
89    /// });
90    /// ```
91    pub fn for_each(&mut self, mut f: impl FnMut(Chunk<'_, I>)) {
92        while let Some(item) = self.next() {
93            f(item)
94        }
95    }
96}
97
98/// An iterator over a chunk of data.
99///
100/// Unlike [`Chunks`], `Chuuk` implements `Iterator` and can be used in for
101/// loop.
102///
103/// This `struct` is created by [`Chunks::next`].
104pub struct Chunk<'a, I: Iterator> {
105    first: Option<I::Item>,
106    parent: &'a mut Chunks<I>,
107    n: usize,
108}
109
110impl<'a, I> Iterator for Chunk<'a, I>
111where
112    I: Iterator,
113{
114    type Item = <I as Iterator>::Item;
115
116    fn next(&mut self) -> Option<Self::Item> {
117        match self.first.take() {
118            Some(v) => Some(v),
119            None if self.n > 0 => {
120                self.n -= 1;
121                match self.parent.inner.next() {
122                    Some(v) => Some(v),
123                    None => {
124                        // The current chunk iterator should output None and end forever.
125                        self.n = 0;
126
127                        // The parent chunks iterator should output None once.
128                        self.parent.end_flag = true;
129
130                        None
131                    }
132                }
133            }
134            None => None,
135        }
136    }
137
138    fn size_hint(&self) -> (usize, Option<usize>) {
139        let (lower, upper) = self.parent.inner.size_hint();
140        let has_first = self.first.is_some() as usize;
141        let n = self.n;
142        // SAFETY: `checked_add` is unnecessary here since n is always less than
143        // `usize::MAX`.
144        let lower = lower.min(n) + has_first;
145        let upper = upper.map(|v| v.min(n) + has_first);
146        (lower, upper)
147    }
148}
149
150#[cfg(test)]
151mod tests {
152    use super::IterChunks;
153
154    #[test]
155    fn test_impls() {
156        let chunks = [0i32].into_iter().chunks(1);
157
158        // A helper function that asserts a type impl Send.
159        fn assert_send<T: Send>(_: &T) {}
160        // A helper function that asserts a type impl Sync.
161        fn assert_sync<T: Sync>(_: &T) {}
162
163        assert_sync(&chunks);
164        assert_send(&chunks);
165    }
166
167    #[test]
168    fn test_chunks() {
169        let arr = [0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3];
170        let mut i = 0;
171        let mut chunks = arr.into_iter().chunks(3);
172
173        while let Some(chunk) = chunks.next() {
174            for v in chunk {
175                assert_eq!(v, i);
176            }
177            i += 1;
178        }
179        assert_eq!(i, 4);
180    }
181
182    #[test]
183    fn test_chunk_resumable() {
184        let inner_gen = |rem| {
185            let mut i = 0;
186            std::iter::from_fn(move || {
187                i += 1;
188                if i % rem == 0 {
189                    None
190                } else {
191                    Some(i)
192                }
193            })
194        };
195
196        let inner = inner_gen(3);
197        let mut chunks = inner.chunks(4);
198        while let Some(chunk) = chunks.next() {
199            assert_eq!(chunk.collect::<Vec<_>>(), vec![1, 2]);
200        }
201        while let Some(chunk) = chunks.next() {
202            assert_eq!(chunk.collect::<Vec<_>>(), vec![4, 5]);
203        }
204        while let Some(chunk) = chunks.next() {
205            assert_eq!(chunk.collect::<Vec<_>>(), vec![7, 8]);
206        }
207
208        let inner = inner_gen(6);
209        let mut chunks = inner.chunks(4);
210
211        assert_eq!(chunks.next().unwrap().collect::<Vec<_>>(), vec![1, 2, 3, 4]);
212        assert_eq!(chunks.next().unwrap().collect::<Vec<_>>(), vec![5]);
213        assert!(chunks.next().is_none());
214
215        assert_eq!(
216            chunks.next().unwrap().collect::<Vec<_>>(),
217            vec![7, 8, 9, 10]
218        );
219        assert_eq!(chunks.next().unwrap().collect::<Vec<_>>(), vec![11]);
220        assert!(chunks.next().is_none());
221    }
222
223    #[test]
224    fn test_chunks_count() {
225        let arr: [bool; 0] = [];
226        let mut i = 0;
227        let mut chunks = arr.into_iter().chunks(3);
228
229        while let Some(chunk) = chunks.next() {
230            for _ in chunk {}
231            i += 1;
232        }
233        assert_eq!(i, 0);
234
235        let arr: [bool; 3] = [false; 3];
236        let mut i = 0;
237        let mut chunks = arr.into_iter().chunks(3);
238
239        while let Some(chunk) = chunks.next() {
240            for _ in chunk {}
241            i += 1;
242        }
243        assert_eq!(i, 1);
244    }
245
246    #[test]
247    fn test_size_hint() {
248        let iter = [1, 2, 3, 4]
249            .into_iter()
250            .chain([5, 6, 7].into_iter().filter(|_| true));
251        let (lower, upper) = iter.size_hint();
252        assert_eq!(lower, 4);
253        assert_eq!(upper, Some(7));
254        let mut chunks = iter.chunks(3);
255
256        let mut chunk1 = chunks.next().unwrap();
257
258        assert_eq!(chunk1.size_hint(), (3, Some(3)));
259        chunk1.next().unwrap();
260        assert_eq!(chunk1.size_hint(), (2, Some(2)));
261
262        for _ in chunk1 {}
263
264        let chunk2 = chunks.next().unwrap();
265        assert_eq!(chunk2.size_hint(), (1, Some(3)));
266        for _ in chunk2 {}
267
268        let mut chunk3 = chunks.next().unwrap();
269        assert_eq!(chunk3.size_hint(), (1, Some(1)));
270        chunk3.next().unwrap();
271        assert_eq!(chunk3.size_hint(), (0, Some(0)));
272    }
273}