Skip to content

Commit a7489e4

Browse files
committed
array_chunks
1 parent 9238090 commit a7489e4

File tree

3 files changed

+157
-1
lines changed

3 files changed

+157
-1
lines changed

src/array_chunks.rs

+82
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
use alloc::vec::Vec;
2+
3+
use crate::next_array::ArrayBuilder;
4+
5+
/// An iterator that groups the items in arrays of const generic size `N`.
6+
///
7+
/// See [`.next_array()`](crate::Itertools::next_array) for details.
8+
#[derive(Debug, Clone)]
9+
pub struct ArrayChunks<I: Iterator, const N: usize> {
10+
iter: I,
11+
partial: Vec<I::Item>,
12+
}
13+
14+
impl<I: Iterator, const N: usize> ArrayChunks<I, N> {
15+
pub(crate) fn new(iter: I) -> Self {
16+
// TODO should we use iter.fuse() instead? Otherwise remainder may behave strangely
17+
Self {
18+
iter,
19+
partial: Vec::new(),
20+
}
21+
}
22+
23+
/// Returns an iterator that yields all the items that have
24+
/// not been included in any of the arrays. Use this to access the
25+
/// leftover elements if the total number of elements yielded by
26+
/// the original iterator is not a multiple of `N`.
27+
///
28+
/// If `self` is not exhausted (i.e. `next()` has not returned `None`)
29+
/// then the iterator returned by `remainder()` will also include
30+
/// the elements that *would* have been included in the arrays
31+
/// produced by `next()`.
32+
///
33+
/// ```
34+
/// use itertools::Itertools;
35+
///
36+
/// let mut it = (1..9).array_chunks();
37+
/// assert_eq!(Some([1, 2, 3]), it.next());
38+
/// assert_eq!(Some([4, 5, 6]), it.next());
39+
/// assert_eq!(None, it.next());
40+
/// itertools::assert_equal(it.remainder(), [7,8]);
41+
///
42+
/// let mut it = (1..9).array_chunks();
43+
/// assert_eq!(Some([1, 2, 3]), it.next());
44+
/// itertools::assert_equal(it.remainder(), 4..9);
45+
/// ```
46+
pub fn remainder(self) -> impl Iterator<Item = I::Item> {
47+
self.partial.into_iter().chain(self.iter)
48+
}
49+
}
50+
51+
impl<I: Iterator, const N: usize> Iterator for ArrayChunks<I, N> {
52+
type Item = [I::Item; N];
53+
54+
fn next(&mut self) -> Option<Self::Item> {
55+
if !self.partial.is_empty() {
56+
return None;
57+
}
58+
let mut builder = ArrayBuilder::new();
59+
for _ in 0..N {
60+
if let Some(item) = self.iter.next() {
61+
builder.push(item);
62+
} else {
63+
break;
64+
}
65+
}
66+
if let Some(array) = builder.take() {
67+
Some(array)
68+
} else {
69+
self.partial = builder.into_vec();
70+
None
71+
}
72+
}
73+
74+
fn size_hint(&self) -> (usize, Option<usize>) {
75+
if N == 0 {
76+
(usize::MAX, None)
77+
} else {
78+
let (lo, hi) = self.iter.size_hint();
79+
(lo / N, hi.map(|hi| hi / N))
80+
}
81+
}
82+
}

src/lib.rs

+55
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,8 @@ pub mod structs {
9797
TakeWhileRef, TupleCombinations, Update, WhileSome,
9898
};
9999
#[cfg(feature = "use_alloc")]
100+
pub use crate::array_chunks::ArrayChunks;
101+
#[cfg(feature = "use_alloc")]
100102
pub use crate::combinations::{ArrayCombinations, Combinations};
101103
#[cfg(feature = "use_alloc")]
102104
pub use crate::combinations_with_replacement::CombinationsWithReplacement;
@@ -171,6 +173,8 @@ pub use crate::unziptuple::{multiunzip, MultiUnzip};
171173
pub use crate::with_position::Position;
172174
pub use crate::ziptuple::multizip;
173175
mod adaptors;
176+
#[cfg(feature = "use_alloc")]
177+
mod array_chunks;
174178
mod either_or_both;
175179
pub use crate::either_or_both::EitherOrBoth;
176180
#[doc(hidden)]
@@ -741,6 +745,57 @@ pub trait Itertools: Iterator {
741745
groupbylazy::new_chunks(self, size)
742746
}
743747

748+
/// Return an iterator that groups the items in arrays of const generic size `N`.
749+
///
750+
/// Use the method `.remainder()` to access leftover items in case
751+
/// the number of items yielded by the original iterator is not a multiple of `N`.
752+
///
753+
/// If `N` is 0, the resulting iterator will be equivalent to `repeat([])`, i.e.
754+
/// `next()` will always return `Some([])`.
755+
///
756+
/// See also the method [`.next_array()`](Itertools::next_array).
757+
///
758+
/// ```
759+
/// use itertools::Itertools;
760+
/// let mut v = Vec::new();
761+
/// for [a, b] in (1..5).array_chunks() {
762+
/// v.push([a, b]);
763+
/// }
764+
/// assert_eq!(v, vec![[1, 2], [3, 4]]);
765+
///
766+
/// let mut it = (1..9).array_chunks();
767+
/// assert_eq!(Some([1, 2, 3]), it.next());
768+
/// assert_eq!(Some([4, 5, 6]), it.next());
769+
/// assert_eq!(None, it.next());
770+
/// itertools::assert_equal(it.remainder(), [7,8]);
771+
///
772+
/// // this requires a type hint
773+
/// let it = (1..7).array_chunks::<3>();
774+
/// itertools::assert_equal(it, vec![[1, 2, 3], [4, 5, 6]]);
775+
///
776+
/// // you can also specify the complete type
777+
/// use itertools::ArrayChunks;
778+
/// use std::ops::Range;
779+
///
780+
/// let it: ArrayChunks<Range<u32>, 3> = (1..7).array_chunks();
781+
/// itertools::assert_equal(it, vec![[1, 2, 3], [4, 5, 6]]);
782+
///
783+
/// let mut it = (1..3).array_chunks::<0>();
784+
/// assert_eq!(it.next(), Some([]));
785+
/// assert_eq!(it.next(), Some([]));
786+
/// // and so on for any further calls to `it.next()`
787+
/// itertools::assert_equal(it.remainder(), 1..3);
788+
/// ```
789+
///
790+
/// See also [`Tuples::into_buffer`].
791+
#[cfg(feature = "use_alloc")]
792+
fn array_chunks<const N: usize>(self) -> ArrayChunks<Self, N>
793+
where
794+
Self: Sized,
795+
{
796+
ArrayChunks::new(self)
797+
}
798+
744799
/// Return an iterator over all contiguous windows producing tuples of
745800
/// a specific size (up to 12).
746801
///

src/next_array.rs

+20-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
#[cfg(feature = "use_alloc")]
2+
use alloc::vec::Vec;
13
use core::mem::{self, MaybeUninit};
24

35
/// An array of at most `N` elements.
4-
struct ArrayBuilder<T, const N: usize> {
6+
pub(crate) struct ArrayBuilder<T, const N: usize> {
57
/// The (possibly uninitialized) elements of the `ArrayBuilder`.
68
///
79
/// # Safety
@@ -86,6 +88,23 @@ impl<T, const N: usize> ArrayBuilder<T, N> {
8688
None
8789
}
8890
}
91+
92+
#[cfg(feature = "use_alloc")]
93+
pub(crate) fn into_vec(mut self) -> Vec<T> {
94+
let len = self.len;
95+
// SAFETY: Decreasing the value of `self.len` cannot violate the
96+
// safety invariant on `self.arr`.
97+
self.len = 0;
98+
(0..len)
99+
.map(|i| {
100+
// SAFETY: Since `self.len` is 0, `self.arr` may safely contain
101+
// uninitialized elements.
102+
let item = mem::replace(&mut self.arr[i], MaybeUninit::uninit());
103+
// SAFETY: we know that item is valid since i < len
104+
unsafe { item.assume_init() }
105+
})
106+
.collect()
107+
}
89108
}
90109

91110
impl<T, const N: usize> AsMut<[T]> for ArrayBuilder<T, N> {

0 commit comments

Comments
 (0)