Skip to content

Commit 2dac0f1

Browse files
authored
Rollup merge of rust-lang#100026 - WaffleLapkin:array-chunks, r=scottmcm
Add `Iterator::array_chunks` (take N+1) A revival of rust-lang#92393. r? `@Mark-Simulacrum` cc `@rossmacarthur` `@scottmcm` `@the8472` I've tried to address most of the review comments on the previous attempt. The only thing I didn't address is `try_fold` implementation, I've left the "custom" one for now, not sure what exactly should it use.
2 parents 6189cba + 5fbcde1 commit 2dac0f1

File tree

7 files changed

+435
-1
lines changed

7 files changed

+435
-1
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
use crate::array;
2+
use crate::iter::{ByRefSized, FusedIterator, Iterator};
3+
use crate::ops::{ControlFlow, NeverShortCircuit, Try};
4+
5+
/// An iterator over `N` elements of the iterator at a time.
6+
///
7+
/// The chunks do not overlap. If `N` does not divide the length of the
8+
/// iterator, then the last up to `N-1` elements will be omitted.
9+
///
10+
/// This `struct` is created by the [`array_chunks`][Iterator::array_chunks]
11+
/// method on [`Iterator`]. See its documentation for more.
12+
#[derive(Debug, Clone)]
13+
#[must_use = "iterators are lazy and do nothing unless consumed"]
14+
#[unstable(feature = "iter_array_chunks", reason = "recently added", issue = "100450")]
15+
pub struct ArrayChunks<I: Iterator, const N: usize> {
16+
iter: I,
17+
remainder: Option<array::IntoIter<I::Item, N>>,
18+
}
19+
20+
impl<I, const N: usize> ArrayChunks<I, N>
21+
where
22+
I: Iterator,
23+
{
24+
#[track_caller]
25+
pub(in crate::iter) fn new(iter: I) -> Self {
26+
assert!(N != 0, "chunk size must be non-zero");
27+
Self { iter, remainder: None }
28+
}
29+
30+
/// Returns an iterator over the remaining elements of the original iterator
31+
/// that are not going to be returned by this iterator. The returned
32+
/// iterator will yield at most `N-1` elements.
33+
#[unstable(feature = "iter_array_chunks", reason = "recently added", issue = "100450")]
34+
#[inline]
35+
pub fn into_remainder(self) -> Option<array::IntoIter<I::Item, N>> {
36+
self.remainder
37+
}
38+
}
39+
40+
#[unstable(feature = "iter_array_chunks", reason = "recently added", issue = "100450")]
41+
impl<I, const N: usize> Iterator for ArrayChunks<I, N>
42+
where
43+
I: Iterator,
44+
{
45+
type Item = [I::Item; N];
46+
47+
#[inline]
48+
fn next(&mut self) -> Option<Self::Item> {
49+
self.try_for_each(ControlFlow::Break).break_value()
50+
}
51+
52+
#[inline]
53+
fn size_hint(&self) -> (usize, Option<usize>) {
54+
let (lower, upper) = self.iter.size_hint();
55+
56+
(lower / N, upper.map(|n| n / N))
57+
}
58+
59+
#[inline]
60+
fn count(self) -> usize {
61+
self.iter.count() / N
62+
}
63+
64+
fn try_fold<B, F, R>(&mut self, init: B, mut f: F) -> R
65+
where
66+
Self: Sized,
67+
F: FnMut(B, Self::Item) -> R,
68+
R: Try<Output = B>,
69+
{
70+
let mut acc = init;
71+
loop {
72+
match self.iter.next_chunk() {
73+
Ok(chunk) => acc = f(acc, chunk)?,
74+
Err(remainder) => {
75+
// Make sure to not override `self.remainder` with an empty array
76+
// when `next` is called after `ArrayChunks` exhaustion.
77+
self.remainder.get_or_insert(remainder);
78+
79+
break try { acc };
80+
}
81+
}
82+
}
83+
}
84+
85+
fn fold<B, F>(mut self, init: B, f: F) -> B
86+
where
87+
Self: Sized,
88+
F: FnMut(B, Self::Item) -> B,
89+
{
90+
self.try_fold(init, NeverShortCircuit::wrap_mut_2(f)).0
91+
}
92+
}
93+
94+
#[unstable(feature = "iter_array_chunks", reason = "recently added", issue = "100450")]
95+
impl<I, const N: usize> DoubleEndedIterator for ArrayChunks<I, N>
96+
where
97+
I: DoubleEndedIterator + ExactSizeIterator,
98+
{
99+
#[inline]
100+
fn next_back(&mut self) -> Option<Self::Item> {
101+
self.try_rfold((), |(), x| ControlFlow::Break(x)).break_value()
102+
}
103+
104+
fn try_rfold<B, F, R>(&mut self, init: B, mut f: F) -> R
105+
where
106+
Self: Sized,
107+
F: FnMut(B, Self::Item) -> R,
108+
R: Try<Output = B>,
109+
{
110+
// We are iterating from the back we need to first handle the remainder.
111+
self.next_back_remainder();
112+
113+
let mut acc = init;
114+
let mut iter = ByRefSized(&mut self.iter).rev();
115+
116+
// NB remainder is handled by `next_back_remainder`, so
117+
// `next_chunk` can't return `Err` with non-empty remainder
118+
// (assuming correct `I as ExactSizeIterator` impl).
119+
while let Ok(mut chunk) = iter.next_chunk() {
120+
// FIXME: do not do double reverse
121+
// (we could instead add `next_chunk_back` for example)
122+
chunk.reverse();
123+
acc = f(acc, chunk)?
124+
}
125+
126+
try { acc }
127+
}
128+
129+
fn rfold<B, F>(mut self, init: B, f: F) -> B
130+
where
131+
Self: Sized,
132+
F: FnMut(B, Self::Item) -> B,
133+
{
134+
self.try_rfold(init, NeverShortCircuit::wrap_mut_2(f)).0
135+
}
136+
}
137+
138+
impl<I, const N: usize> ArrayChunks<I, N>
139+
where
140+
I: DoubleEndedIterator + ExactSizeIterator,
141+
{
142+
/// Updates `self.remainder` such that `self.iter.len` is divisible by `N`.
143+
fn next_back_remainder(&mut self) {
144+
// Make sure to not override `self.remainder` with an empty array
145+
// when `next_back` is called after `ArrayChunks` exhaustion.
146+
if self.remainder.is_some() {
147+
return;
148+
}
149+
150+
// We use the `ExactSizeIterator` implementation of the underlying
151+
// iterator to know how many remaining elements there are.
152+
let rem = self.iter.len() % N;
153+
154+
// Take the last `rem` elements out of `self.iter`.
155+
let mut remainder =
156+
// SAFETY: `unwrap_err` always succeeds because x % N < N for all x.
157+
unsafe { self.iter.by_ref().rev().take(rem).next_chunk().unwrap_err_unchecked() };
158+
159+
// We used `.rev()` above, so we need to re-reverse the reminder
160+
remainder.as_mut_slice().reverse();
161+
self.remainder = Some(remainder);
162+
}
163+
}
164+
165+
#[unstable(feature = "iter_array_chunks", reason = "recently added", issue = "100450")]
166+
impl<I, const N: usize> FusedIterator for ArrayChunks<I, N> where I: FusedIterator {}
167+
168+
#[unstable(feature = "iter_array_chunks", reason = "recently added", issue = "100450")]
169+
impl<I, const N: usize> ExactSizeIterator for ArrayChunks<I, N>
170+
where
171+
I: ExactSizeIterator,
172+
{
173+
#[inline]
174+
fn len(&self) -> usize {
175+
self.iter.len() / N
176+
}
177+
178+
#[inline]
179+
fn is_empty(&self) -> bool {
180+
self.iter.len() < N
181+
}
182+
}

library/core/src/iter/adapters/mod.rs

+4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use crate::iter::{InPlaceIterable, Iterator};
22
use crate::ops::{ChangeOutputType, ControlFlow, FromResidual, NeverShortCircuit, Residual, Try};
33

4+
mod array_chunks;
45
mod by_ref_sized;
56
mod chain;
67
mod cloned;
@@ -32,6 +33,9 @@ pub use self::{
3233
scan::Scan, skip::Skip, skip_while::SkipWhile, take::Take, take_while::TakeWhile, zip::Zip,
3334
};
3435

36+
#[unstable(feature = "iter_array_chunks", reason = "recently added", issue = "100450")]
37+
pub use self::array_chunks::ArrayChunks;
38+
3539
#[unstable(feature = "std_internals", issue = "none")]
3640
pub use self::by_ref_sized::ByRefSized;
3741

library/core/src/iter/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,8 @@ pub use self::traits::{
398398

399399
#[stable(feature = "iter_zip", since = "1.59.0")]
400400
pub use self::adapters::zip;
401+
#[unstable(feature = "iter_array_chunks", reason = "recently added", issue = "100450")]
402+
pub use self::adapters::ArrayChunks;
401403
#[unstable(feature = "std_internals", issue = "none")]
402404
pub use self::adapters::ByRefSized;
403405
#[stable(feature = "iter_cloned", since = "1.1.0")]

library/core/src/iter/traits/iterator.rs

+44-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use crate::ops::{ChangeOutputType, ControlFlow, FromResidual, Residual, Try};
55
use super::super::try_process;
66
use super::super::ByRefSized;
77
use super::super::TrustedRandomAccessNoCoerce;
8-
use super::super::{Chain, Cloned, Copied, Cycle, Enumerate, Filter, FilterMap, Fuse};
8+
use super::super::{ArrayChunks, Chain, Cloned, Copied, Cycle, Enumerate, Filter, FilterMap, Fuse};
99
use super::super::{FlatMap, Flatten};
1010
use super::super::{FromIterator, Intersperse, IntersperseWith, Product, Sum, Zip};
1111
use super::super::{
@@ -3316,6 +3316,49 @@ pub trait Iterator {
33163316
Cycle::new(self)
33173317
}
33183318

3319+
/// Returns an iterator over `N` elements of the iterator at a time.
3320+
///
3321+
/// The chunks do not overlap. If `N` does not divide the length of the
3322+
/// iterator, then the last up to `N-1` elements will be omitted and can be
3323+
/// retrieved from the [`.into_remainder()`][ArrayChunks::into_remainder]
3324+
/// function of the iterator.
3325+
///
3326+
/// # Panics
3327+
///
3328+
/// Panics if `N` is 0.
3329+
///
3330+
/// # Examples
3331+
///
3332+
/// Basic usage:
3333+
///
3334+
/// ```
3335+
/// #![feature(iter_array_chunks)]
3336+
///
3337+
/// let mut iter = "lorem".chars().array_chunks();
3338+
/// assert_eq!(iter.next(), Some(['l', 'o']));
3339+
/// assert_eq!(iter.next(), Some(['r', 'e']));
3340+
/// assert_eq!(iter.next(), None);
3341+
/// assert_eq!(iter.into_remainder().unwrap().as_slice(), &['m']);
3342+
/// ```
3343+
///
3344+
/// ```
3345+
/// #![feature(iter_array_chunks)]
3346+
///
3347+
/// let data = [1, 1, 2, -2, 6, 0, 3, 1];
3348+
/// // ^-----^ ^------^
3349+
/// for [x, y, z] in data.iter().array_chunks() {
3350+
/// assert_eq!(x + y + z, 4);
3351+
/// }
3352+
/// ```
3353+
#[track_caller]
3354+
#[unstable(feature = "iter_array_chunks", reason = "recently added", issue = "100450")]
3355+
fn array_chunks<const N: usize>(self) -> ArrayChunks<Self, N>
3356+
where
3357+
Self: Sized,
3358+
{
3359+
ArrayChunks::new(self)
3360+
}
3361+
33193362
/// Sums the elements of an iterator.
33203363
///
33213364
/// Takes each element, adds them together, and returns the result.

0 commit comments

Comments
 (0)