Skip to content

Commit 7afc0fa

Browse files
committed
New method circular_array_windows().
This is very like the existing `circular_tuple_windows`, but imposes the minimum possible bounds on the input iterator: it must have cloneable items because each item is returned N times, and it must be Sized so that it can be stored in a struct. Unlike `circular_tuple_windows`, it doesn't require the input iterator itself to have extra traits, like Clone or ExactSizeIterator. Because the return type is an array (as suggested in #1084), we must handle the zero-length case, because you can't have a constraint `N>0`. In that situation we still read to the end of the input iterator, discard each item as we read it, and return a zero-length array per item, preserving the invariant that this iterator is the same length as the input one.
1 parent 6c2e8f6 commit 7afc0fa

5 files changed

Lines changed: 396 additions & 0 deletions

File tree

src/array_impl.rs

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
use std::iter::ExactSizeIterator;
2+
3+
/// An iterator over all windows, wrapping back to the first elements when the
4+
/// window would otherwise exceed the length of the iterator, producing arrays
5+
/// of a specific size.
6+
///
7+
/// See [`.circular_array_windows()`](crate::Itertools::circular_array_windows)
8+
/// for more information.
9+
#[derive(Debug, Clone)]
10+
pub struct CircularArrayWindows<I, const N: usize>
11+
where
12+
I: Iterator + Sized,
13+
I::Item: Clone,
14+
{
15+
iter: I,
16+
inner: Option<CircularArrayWindowsInner<I::Item, N>>,
17+
}
18+
19+
#[derive(Debug, Clone)]
20+
struct CircularArrayWindowsInner<T: Clone, const N: usize> {
21+
prefix: [T; N],
22+
prefix_pos: Option<usize>,
23+
ringbuf: [T; N],
24+
ringpos: usize,
25+
}
26+
27+
impl<T: Clone, const N: usize> CircularArrayWindowsInner<T, N> {
28+
fn new(first: T, iter: &mut impl Iterator<Item = T>) -> Self {
29+
let mut items = std::array::from_fn(|_| None);
30+
let mut prefix_pos = None;
31+
if N > 0 {
32+
items[0] = Some(first);
33+
}
34+
for i in 1..N {
35+
let item = iter.next();
36+
if item.is_none() {
37+
for j in i..N {
38+
items[j] = items[j - i].clone();
39+
}
40+
prefix_pos = Some(N - i);
41+
break;
42+
}
43+
items[i] = item;
44+
}
45+
let items = items.map(Option::unwrap);
46+
Self {
47+
prefix: items.clone(),
48+
prefix_pos,
49+
ringbuf: items,
50+
ringpos: 0,
51+
}
52+
}
53+
54+
/// Read the next item in the logical input sequence (consisting
55+
/// of the contents of the input iterator followed by N-1 items
56+
/// recycling from the beginning). Add it to the ring buffer.
57+
fn read_item(&mut self, iter: &mut impl Iterator<Item = T>) -> bool {
58+
let item = if let Some(pos) = &mut self.prefix_pos {
59+
// The input iterator has already run out, so clone an
60+
// element from `prefix`, wrapping round to the start as
61+
// necessary.
62+
let item = self.prefix[*pos].clone();
63+
*pos += 1;
64+
item
65+
} else if let Some(item) = iter.next() {
66+
// Read from the input iterator.
67+
item
68+
} else if N > 1 {
69+
// The input iterator has run out right now, so clone the
70+
// first element of `prefix`, and set cyclepos to point to
71+
// the next one.
72+
self.prefix_pos = Some(1);
73+
self.prefix[0].clone()
74+
} else {
75+
// Special case if N=1: don't read an item at all if the
76+
// input iterator has run out.
77+
self.prefix_pos = Some(0);
78+
return false;
79+
};
80+
81+
if N > 0 {
82+
self.ringbuf[self.ringpos] = item;
83+
self.ringpos = (self.ringpos + 1) % N;
84+
}
85+
true
86+
}
87+
88+
/// Construct an array window to return, given the newly read item
89+
/// to go on the end of the output.
90+
fn make_window(&mut self) -> [T; N] {
91+
std::array::from_fn(|i| self.ringbuf[(i + self.ringpos) % N].clone())
92+
}
93+
}
94+
95+
impl<I, const N: usize> Iterator for CircularArrayWindows<I, N>
96+
where
97+
I: Iterator + Sized,
98+
I::Item: Clone,
99+
{
100+
type Item = [I::Item; N];
101+
102+
fn next(&mut self) -> Option<[I::Item; N]> {
103+
match &mut self.inner {
104+
// Initialisation code, when next() is called for the first time
105+
None => match self.iter.next() {
106+
None => {
107+
// The input iterator was completely empty
108+
None
109+
}
110+
Some(first) => {
111+
// We have at least one item, so we can definitely
112+
// populate `prefix` (even if we have to make N
113+
// copies of this element).
114+
115+
let mut inner = CircularArrayWindowsInner::new(first, &mut self.iter);
116+
let window = inner.make_window();
117+
self.inner = Some(inner);
118+
Some(window)
119+
}
120+
},
121+
Some(inner) => {
122+
if inner.prefix_pos.is_some_and(|pos| pos + 1 >= N) {
123+
// The input iterator has run out, and we've
124+
// emitted as many windows as we read items, so
125+
// we've finished.
126+
None
127+
} else {
128+
// Normal case. Fetch an item and return a window.
129+
if inner.read_item(&mut self.iter) {
130+
Some(inner.make_window())
131+
} else {
132+
None
133+
}
134+
}
135+
}
136+
}
137+
}
138+
}
139+
140+
// We return exactly one window per input item, so if the input
141+
// iterator knows its length, then so do we.
142+
impl<I, const N: usize> ExactSizeIterator for CircularArrayWindows<I, N>
143+
where
144+
I: Iterator + Sized + ExactSizeIterator,
145+
I::Item: Clone,
146+
{
147+
fn len(&self) -> usize {
148+
self.iter.len()
149+
}
150+
}
151+
152+
pub fn circular_array_windows<I, const N: usize>(iter: I) -> CircularArrayWindows<I, N>
153+
where
154+
I: Iterator + Sized,
155+
I::Item: Clone,
156+
{
157+
CircularArrayWindows { iter, inner: None }
158+
}

src/lib.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ pub mod structs {
9797
FilterOk, Interleave, InterleaveShortest, MapInto, MapOk, Positions, Product, PutBack,
9898
TakeWhileRef, TupleCombinations, Update, WhileSome,
9999
};
100+
pub use crate::array_impl::CircularArrayWindows;
100101
#[cfg(feature = "use_alloc")]
101102
pub use crate::combinations::{ArrayCombinations, Combinations};
102103
#[cfg(feature = "use_alloc")]
@@ -174,6 +175,7 @@ pub use crate::unziptuple::{multiunzip, MultiUnzip};
174175
pub use crate::with_position::Position;
175176
pub use crate::ziptuple::multizip;
176177
mod adaptors;
178+
mod array_impl;
177179
mod either_or_both;
178180
pub use crate::either_or_both::EitherOrBoth;
179181
#[doc(hidden)]
@@ -900,6 +902,38 @@ pub trait Itertools: Iterator {
900902
tuple_impl::tuples(self)
901903
}
902904

905+
/// Return an iterator over all windows, wrapping back to the first
906+
/// elements when the window would otherwise exceed the length of the
907+
/// iterator, producing arrays of size `N`.
908+
///
909+
/// `circular_array_windows` clones the iterator elements so that
910+
/// they can be part of successive windows, this makes it most
911+
/// suited for iterators of references and other values that are
912+
/// cheap to copy.
913+
///
914+
/// ```
915+
/// use itertools::Itertools;
916+
/// let mut v = Vec::new();
917+
/// for [a, b] in (1..5).circular_array_windows() {
918+
/// v.push([a, b]);
919+
/// }
920+
/// assert_eq!(v, vec![[1, 2], [2, 3], [3, 4], [4, 1]]);
921+
///
922+
/// let mut it = (1..5).circular_array_windows();
923+
/// assert_eq!(Some([1, 2, 3]), it.next());
924+
/// assert_eq!(Some([2, 3, 4]), it.next());
925+
/// assert_eq!(Some([3, 4, 1]), it.next());
926+
/// assert_eq!(Some([4, 1, 2]), it.next());
927+
/// assert_eq!(None, it.next());
928+
/// ```
929+
fn circular_array_windows<const N: usize>(self) -> CircularArrayWindows<Self, N>
930+
where
931+
Self: Sized,
932+
Self::Item: Clone,
933+
{
934+
array_impl::circular_array_windows(self)
935+
}
936+
903937
/// Split into an iterator pair that both yield all elements from
904938
/// the original iterator.
905939
///

tests/arrays.rs

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
use itertools::Itertools;
2+
3+
#[test]
4+
fn circular_array_windows() {
5+
let [vec0, vec1, vec2, vec4, vec10] = [
6+
vec![],
7+
vec![1],
8+
vec![1, 2],
9+
vec![1, 2, 3, 4],
10+
vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
11+
];
12+
13+
assert_eq!(
14+
vec10
15+
.iter()
16+
.copied()
17+
.circular_array_windows::<2>()
18+
.collect::<Vec<_>>(),
19+
vec![
20+
[1, 2],
21+
[2, 3],
22+
[3, 4],
23+
[4, 5],
24+
[5, 6],
25+
[6, 7],
26+
[7, 8],
27+
[8, 9],
28+
[9, 10],
29+
[10, 1]
30+
]
31+
);
32+
33+
assert_eq!(
34+
vec4.iter()
35+
.copied()
36+
.circular_array_windows::<2>()
37+
.collect::<Vec<_>>(),
38+
vec![[1, 2], [2, 3], [3, 4], [4, 1]]
39+
);
40+
41+
assert_eq!(
42+
vec2.iter()
43+
.copied()
44+
.circular_array_windows::<2>()
45+
.collect::<Vec<_>>(),
46+
vec![[1, 2], [2, 1]]
47+
);
48+
49+
assert_eq!(
50+
vec1.iter()
51+
.copied()
52+
.circular_array_windows::<2>()
53+
.collect::<Vec<_>>(),
54+
vec![[1, 1]]
55+
);
56+
57+
assert_eq!(
58+
vec0.iter()
59+
.copied()
60+
.circular_array_windows::<2>()
61+
.collect::<Vec<_>>(),
62+
Vec::<[i32; 2]>::new()
63+
);
64+
65+
assert_eq!(
66+
vec10
67+
.iter()
68+
.copied()
69+
.circular_array_windows::<4>()
70+
.collect::<Vec<_>>(),
71+
vec![
72+
[1, 2, 3, 4],
73+
[2, 3, 4, 5],
74+
[3, 4, 5, 6],
75+
[4, 5, 6, 7],
76+
[5, 6, 7, 8],
77+
[6, 7, 8, 9],
78+
[7, 8, 9, 10],
79+
[8, 9, 10, 1],
80+
[9, 10, 1, 2],
81+
[10, 1, 2, 3],
82+
]
83+
);
84+
85+
assert_eq!(
86+
vec0.iter()
87+
.copied()
88+
.circular_array_windows::<0>()
89+
.collect::<Vec<_>>(),
90+
Vec::<[i32; 0]>::new()
91+
);
92+
93+
assert_eq!(
94+
vec1.iter()
95+
.copied()
96+
.circular_array_windows::<0>()
97+
.collect::<Vec<_>>(),
98+
vec![[]]
99+
);
100+
101+
assert_eq!(
102+
vec2.iter()
103+
.copied()
104+
.circular_array_windows::<0>()
105+
.collect::<Vec<_>>(),
106+
vec![[], []]
107+
);
108+
109+
assert_eq!(
110+
vec0.iter()
111+
.copied()
112+
.circular_array_windows::<1>()
113+
.collect::<Vec<_>>(),
114+
Vec::<[i32; 1]>::new()
115+
);
116+
117+
assert_eq!(
118+
vec1.iter()
119+
.copied()
120+
.circular_array_windows::<1>()
121+
.collect::<Vec<_>>(),
122+
vec![[1]]
123+
);
124+
125+
assert_eq!(
126+
vec2.iter()
127+
.copied()
128+
.circular_array_windows::<1>()
129+
.collect::<Vec<_>>(),
130+
vec![[1], [2]]
131+
);
132+
133+
assert_eq!(
134+
vec1.iter()
135+
.copied()
136+
.circular_array_windows::<7>()
137+
.collect::<Vec<_>>(),
138+
vec![[1, 1, 1, 1, 1, 1, 1]]
139+
);
140+
141+
assert_eq!(
142+
vec2.iter()
143+
.copied()
144+
.circular_array_windows::<7>()
145+
.collect::<Vec<_>>(),
146+
vec![[1, 2, 1, 2, 1, 2, 1], [2, 1, 2, 1, 2, 1, 2]]
147+
);
148+
149+
assert_eq!(
150+
vec4.iter()
151+
.copied()
152+
.circular_array_windows::<7>()
153+
.collect::<Vec<_>>(),
154+
vec![[1, 2, 1, 2, 1, 2, 1], [2, 1, 2, 1, 2, 1, 2]]
155+
);
156+
}

tests/laziness.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,12 @@ must_use_tests! {
104104
let _ = Panicking.circular_tuple_windows::<(_, _)>();
105105
let _ = Panicking.circular_tuple_windows::<(_, _, _)>();
106106
}
107+
circular_array_windows {
108+
let _ = Panicking.circular_array_windows::<0>();
109+
let _ = Panicking.circular_array_windows::<1>();
110+
let _ = Panicking.circular_array_windows::<2>();
111+
let _ = Panicking.circular_array_windows::<3>();
112+
}
107113
tuples {
108114
let _ = Panicking.tuples::<(_,)>();
109115
let _ = Panicking.tuples::<(_, _)>();

0 commit comments

Comments
 (0)