Skip to content

Commit fe3c505

Browse files
zip_squash & zip_stretch
Introduces new zip alternatives.
1 parent f80883b commit fe3c505

File tree

4 files changed

+244
-1
lines changed

4 files changed

+244
-1
lines changed

src/free.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ pub use crate::put_back_n_impl::put_back_n;
2828
#[cfg(feature = "use_alloc")]
2929
pub use crate::rciter_impl::rciter;
3030
pub use crate::zip_eq_impl::zip_eq;
31+
pub use crate::zip_squash::zip_squash;
32+
pub use crate::zip_stretch::zip_stretch;
3133

3234
/// Iterate `iterable` with a particular value inserted between each element.
3335
///

src/lib.rs

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,8 @@ pub mod structs {
144144
pub use crate::with_position::WithPosition;
145145
pub use crate::zip_eq_impl::ZipEq;
146146
pub use crate::zip_longest::ZipLongest;
147+
pub use crate::zip_squash::ZipSquash;
148+
pub use crate::zip_stretch::ZipStretch;
147149
pub use crate::ziptuple::Zip;
148150
}
149151

@@ -235,6 +237,8 @@ mod unziptuple;
235237
mod with_position;
236238
mod zip_eq_impl;
237239
mod zip_longest;
240+
mod zip_squash;
241+
mod zip_stretch;
238242
mod ziptuple;
239243

240244
#[macro_export]
@@ -4537,10 +4541,61 @@ pub trait Itertools: Iterator {
45374541
_ => Err(sh),
45384542
}
45394543
}
4544+
4545+
/// Create an iterator which iterates over both this and the specified
4546+
/// iterator simultaneously, yielding pairs of elements.
4547+
///
4548+
/// Similar to [`Iterator::zip`] except elements are evenly sampled from
4549+
/// the longest iterator.
4550+
///
4551+
/// ```
4552+
/// use itertools::Itertools;
4553+
/// let a = vec![1, 2];
4554+
/// let b = vec![1, 2, 3];
4555+
///
4556+
/// let it = a.into_iter().zip_squash(b.into_iter());
4557+
/// itertools::assert_equal(it, vec![(1, 1),(2,3)]);
4558+
/// ```
4559+
#[inline]
4560+
fn zip_squash<J>(self, other: J) -> ZipSquash<Self, J::IntoIter>
4561+
where
4562+
J: IntoIterator,
4563+
<J as IntoIterator>::IntoIter: ExactSizeIterator,
4564+
Self: ExactSizeIterator + Sized,
4565+
{
4566+
zip_squash::zip_squash(self, other)
4567+
}
4568+
/// Create an iterator which iterates over both this and the specified
4569+
/// iterator simultaneously, yielding pairs of elements.
4570+
///
4571+
/// Always yielding the first and last elements of both iterators by cloning
4572+
/// elements in the shortest iterator.
4573+
///
4574+
/// Similar to [`Itertools::zip_longest`] except elements in the shortest
4575+
/// iterator are evenly spread.
4576+
///
4577+
/// ```
4578+
/// use itertools::Itertools;
4579+
/// let a = vec![1, 2];
4580+
/// let b = vec![1, 2, 3];
4581+
///
4582+
/// let it = a.into_iter().zip_stretch(b.into_iter());
4583+
/// itertools::assert_equal(it, vec![(1, 1),(1,2),(2,3)]);
4584+
/// ```
4585+
#[inline]
4586+
fn zip_stretch<J>(self, other: J) -> ZipStretch<Self, J::IntoIter>
4587+
where
4588+
J: IntoIterator,
4589+
<J as IntoIterator>::IntoIter: ExactSizeIterator,
4590+
<<J as IntoIterator>::IntoIter as IntoIterator>::Item: Clone,
4591+
Self: ExactSizeIterator + Sized,
4592+
<Self as Iterator>::Item: Clone,
4593+
{
4594+
zip_stretch::zip_stretch(self, other)
4595+
}
45404596
}
45414597

45424598
impl<T> Itertools for T where T: Iterator + ?Sized {}
4543-
45444599
/// Return `true` if both iterables produce equal sequences
45454600
/// (elements pairwise equal and sequences of the same length),
45464601
/// `false` otherwise.

src/zip_squash.rs

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
use super::size_hint;
2+
use std::cmp::Ordering;
3+
4+
/// An iterator which iterates two other iterators simultaneously
5+
/// always returning elements are evenly sampled from the longest iterator.
6+
///
7+
/// See [`.zip_squash()`](crate::Itertools::zip_squash) for more information.
8+
#[derive(Clone, Debug)]
9+
#[must_use = "iterator adaptors are lazy and do nothing unless consumed"]
10+
pub struct ZipSquash<I: ExactSizeIterator, J: ExactSizeIterator> {
11+
a: I,
12+
b: J,
13+
a_delta: f32,
14+
b_delta: f32,
15+
a_index: f32,
16+
b_index: f32,
17+
}
18+
19+
/// Zips two iterators skipping elements of the longest iterator to ensure it fully consumes both
20+
/// iterators.
21+
///
22+
/// [`IntoIterator`] enabled version of [`Itertools::zip_squash`](crate::Itertools::zip_squash).
23+
pub fn zip_squash<I, J>(i: I, j: J) -> ZipSquash<I::IntoIter, J::IntoIter>
24+
where
25+
I: IntoIterator,
26+
J: IntoIterator,
27+
<I as IntoIterator>::IntoIter: ExactSizeIterator,
28+
<J as IntoIterator>::IntoIter: ExactSizeIterator,
29+
{
30+
use std::iter::ExactSizeIterator;
31+
let (a, b) = (i.into_iter(), j.into_iter());
32+
let (a_delta, b_delta) = match a.len().cmp(&b.len()) {
33+
Ordering::Equal => (1f32, 1f32),
34+
Ordering::Less => (1f32, b.len() as f32 / a.len() as f32),
35+
Ordering::Greater => (a.len() as f32 / b.len() as f32, 1f32),
36+
};
37+
debug_assert!(a_delta >= 1f32);
38+
debug_assert!(b_delta >= 1f32);
39+
ZipSquash {
40+
a,
41+
b,
42+
a_delta,
43+
b_delta,
44+
a_index: 0f32,
45+
b_index: 0f32,
46+
}
47+
}
48+
49+
impl<I, J> Iterator for ZipSquash<I, J>
50+
where
51+
I: ExactSizeIterator,
52+
J: ExactSizeIterator,
53+
{
54+
type Item = (I::Item, J::Item);
55+
56+
fn next(&mut self) -> Option<Self::Item> {
57+
let (a, b) = (self.a.next(), self.b.next());
58+
let a_diff = (self.a_delta / (1f32 - self.a_index.fract())).ceil() as usize;
59+
self.a_index += a_diff as f32 * self.a_delta;
60+
if let Some(skip) = a_diff.checked_sub(2) {
61+
self.a.nth(skip);
62+
}
63+
64+
let b_diff = (self.b_delta / (1f32 - self.b_index.fract())).ceil() as usize;
65+
self.b_index += b_diff as f32 * self.b_delta;
66+
if let Some(skip) = b_diff.checked_sub(2) {
67+
self.b.nth(skip);
68+
}
69+
70+
match (a, b) {
71+
(None, None) => None,
72+
(Some(a), Some(b)) => Some((a, b)),
73+
(None, Some(_)) | (Some(_), None) => unreachable!(),
74+
}
75+
}
76+
77+
fn size_hint(&self) -> (usize, Option<usize>) {
78+
size_hint::min(self.a.size_hint(), self.b.size_hint())
79+
}
80+
}
81+
82+
impl<I, J> ExactSizeIterator for ZipSquash<I, J>
83+
where
84+
I: ExactSizeIterator,
85+
J: ExactSizeIterator,
86+
{
87+
}

src/zip_stretch.rs

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
use super::size_hint;
2+
use std::cmp::Ordering;
3+
4+
/// An iterator which iterates two other iterators simultaneously
5+
/// always returning the first and last elements of both iterators by using
6+
/// cloning to extend the length of the shortest iterator.
7+
///
8+
/// See [`.zip_stretch()`](crate::Itertools::zip_stretch) for more information.
9+
#[derive(Clone)]
10+
#[must_use = "iterator adaptors are lazy and do nothing unless consumed"]
11+
pub struct ZipStretch<I: ExactSizeIterator, J: ExactSizeIterator>
12+
where
13+
<I as Iterator>::Item: Clone,
14+
<J as Iterator>::Item: Clone,
15+
{
16+
a: I,
17+
b: J,
18+
a_delta: f32,
19+
b_delta: f32,
20+
a_index: f32,
21+
b_index: f32,
22+
a_dupe: Option<<I as Iterator>::Item>,
23+
b_dupe: Option<<J as Iterator>::Item>,
24+
}
25+
26+
/// Zips two iterators cloning elements to extend the length of the shortest iterator to
27+
/// ensure it fully consumes both iterators.
28+
///
29+
/// [`IntoIterator`] enabled version of [`Itertools::zip_stretch`](crate::Itertools::zip_stretch).
30+
pub fn zip_stretch<I, J>(i: I, j: J) -> ZipStretch<I::IntoIter, J::IntoIter>
31+
where
32+
I: IntoIterator,
33+
J: IntoIterator,
34+
<I as IntoIterator>::IntoIter: ExactSizeIterator,
35+
<J as IntoIterator>::IntoIter: ExactSizeIterator,
36+
<<I as IntoIterator>::IntoIter as IntoIterator>::Item: Clone,
37+
<<J as IntoIterator>::IntoIter as IntoIterator>::Item: Clone,
38+
{
39+
use std::iter::ExactSizeIterator;
40+
let (a, b) = (i.into_iter(), j.into_iter());
41+
let (a_delta, b_delta) = match a.len().cmp(&b.len()) {
42+
Ordering::Equal => (1f32, 1f32),
43+
Ordering::Less => (a.len() as f32 / b.len() as f32, 1f32),
44+
Ordering::Greater => (1f32, b.len() as f32 / a.len() as f32),
45+
};
46+
debug_assert!(a_delta <= 1f32);
47+
debug_assert!(b_delta <= 1f32);
48+
ZipStretch {
49+
a,
50+
b,
51+
a_delta,
52+
b_delta,
53+
a_index: 0f32,
54+
b_index: 0f32,
55+
a_dupe: None,
56+
b_dupe: None,
57+
}
58+
}
59+
60+
impl<I, J> Iterator for ZipStretch<I, J>
61+
where
62+
I: ExactSizeIterator,
63+
J: ExactSizeIterator,
64+
<I as Iterator>::Item: Clone,
65+
<J as Iterator>::Item: Clone,
66+
{
67+
type Item = (I::Item, J::Item);
68+
69+
fn next(&mut self) -> Option<Self::Item> {
70+
if self.a_index.fract() < self.a_delta {
71+
self.a_dupe = self.a.next();
72+
}
73+
self.a_index += self.a_delta;
74+
75+
if self.b_index.fract() < self.b_delta {
76+
self.b_dupe = self.b.next();
77+
}
78+
self.b_index += self.b_delta;
79+
80+
match (&self.a_dupe, &self.b_dupe) {
81+
(Some(a), Some(b)) => Some((a.clone(), b.clone())),
82+
(None, Some(_)) | (Some(_), None) => unreachable!(),
83+
(None, None) => None,
84+
}
85+
}
86+
87+
fn size_hint(&self) -> (usize, Option<usize>) {
88+
size_hint::min(self.a.size_hint(), self.b.size_hint())
89+
}
90+
}
91+
92+
impl<I, J> ExactSizeIterator for ZipStretch<I, J>
93+
where
94+
I: ExactSizeIterator,
95+
J: ExactSizeIterator,
96+
<I as Iterator>::Item: Clone,
97+
<J as Iterator>::Item: Clone,
98+
{
99+
}

0 commit comments

Comments
 (0)