Skip to content

Commit 1130e46

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

File tree

4 files changed

+232
-1
lines changed

4 files changed

+232
-1
lines changed

src/free.rs

+2
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

+54-1
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,59 @@ 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 using [`EitherOrBoth`].
4572+
///
4573+
/// Similar to [`Itertools::zip_longest`] except elements in the shortest iterator are evenly
4574+
/// spread.
4575+
///
4576+
/// ```
4577+
/// use itertools::Itertools;
4578+
/// use itertools::EitherOrBoth;
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![EitherOrBoth::Both(1, 1),EitherOrBoth::Right(2), EitherOrBoth::Both(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+
Self: ExactSizeIterator + Sized,
4591+
{
4592+
zip_stretch::zip_stretch(self, other)
4593+
}
45404594
}
45414595

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

src/zip_squash.rs

+87
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

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

0 commit comments

Comments
 (0)