Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add zip_clones, zips an iterator with clones of a value #989

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ pub mod structs {
#[cfg(feature = "use_std")]
pub use crate::unique_impl::{Unique, UniqueBy};
pub use crate::with_position::WithPosition;
pub use crate::zip_clones::ZipClones;
pub use crate::zip_eq_impl::ZipEq;
pub use crate::zip_longest::ZipLongest;
pub use crate::ziptuple::Zip;
Expand Down Expand Up @@ -233,6 +234,7 @@ mod tuple_impl;
mod unique_impl;
mod unziptuple;
mod with_position;
mod zip_clones;
mod zip_eq_impl;
mod zip_longest;
mod ziptuple;
Expand Down Expand Up @@ -617,6 +619,43 @@ pub trait Itertools: Iterator {
zip_eq(self, other)
}

/// Create an iterator which iterates over this iterator paired with clones of a given value.
///
/// If the iterator has `n` elements, the zipped value will be cloned `n-1` times. This function
/// is useful when the zipped value is expensive to clone and you want to avoid cloning it `n` times,
/// using the trivial following code:
/// ```rust
/// let it = [0, 1, 2, 3, 4].iter();
/// let zipped = "expensive-to-clone".to_string();
/// for a in it {
/// let b = zipped.clone();
/// // do something that consumes the expensive zipped value
/// drop((a, b));
/// }
/// ```
/// Instead, you can use `zip_clones`:
/// ```rust
/// use itertools::Itertools;
/// let it = [0, 1, 2, 3, 4].iter();
/// let zipped = "expensive-to-clone".to_string();
/// for (a, b) in it.zip_clones(zipped) {
/// // do something that consumes the expensive zipped value
/// drop((a, b));
/// }
/// ```
///
/// The [`repeat_n()`](crate::repeat_n) function can be used to create from a zipped value
/// an iterator that also clones the value `n-1` times, but it require to know the number of
/// elements in the iterator in advance.
#[inline]
fn zip_clones<T>(self, zipped: T) -> ZipClones<Self, T>
where
Self: Sized,
T: Clone,
{
zip_clones::zip_clones(self, zipped)
}

/// A “meta iterator adaptor”. Its closure receives a reference to the
/// iterator and may pick off as many elements as it likes, to produce the
/// next iterator element.
Expand Down
77 changes: 77 additions & 0 deletions src/zip_clones.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
use std::iter::Peekable;

/// An iterator which iterate over an iterator and clones of a value.
///
///
/// See [`.zip_clones()`](crate::Itertools::zip_clones) for more information.
pub struct ZipClones<I, T>
where
I: Iterator,
{
iter: Peekable<I>,
cloned: Option<T>,
}

/// Zips an iterator with clones of a value.
///
/// [`IntoIterator`] enabled version of [`Itertools::zip_clones`](crate::Itertools::zip_clones).
///
/// ```
/// use itertools::Itertools;
///
/// let data = [1, 2, 3, 4, 5];
/// let zipped = "expensive-to-clone".to_string();
/// for (a, b) in data.iter().zip_clones(zipped) {
/// // do something that consumes the expensive zipped value
/// drop((a, b));
/// }
/// ```
pub fn zip_clones<I, T>(i: I, zipped: T) -> ZipClones<I::IntoIter, T>
where
I: IntoIterator,
T: Clone,
{
ZipClones {
iter: i.into_iter().peekable(),
cloned: Some(zipped),
}
}

impl<I: Iterator, T: Clone> Iterator for ZipClones<I, T> {
type Item = (I::Item, T);
fn next(&mut self) -> Option<Self::Item> {
// let cur = self.next.take()?;
let cur = self.iter.next()?;
let zipped = if self.iter.peek().is_some() {
self.cloned.clone()
} else {
self.cloned.take()
};
// Safety: the zipped field is Some as long as the iterator is not exhausted
let zipped = unsafe { zipped.unwrap_unchecked() };
Some((cur, zipped))
}
}

#[cfg(test)]
mod tests {
use crate::Itertools;
use std::sync::atomic::{AtomicUsize, Ordering};

#[test]
fn test_zip_clones() {
static ZIPPED_CLONES_COUNTER: AtomicUsize = AtomicUsize::new(0);
struct Zipped {}
impl Clone for Zipped {
fn clone(&self) -> Self {
ZIPPED_CLONES_COUNTER.fetch_add(1, Ordering::SeqCst);
Zipped {}
}
}

ZIPPED_CLONES_COUNTER.store(0, Ordering::SeqCst);
let iter_len = [1, 2, 3, 4, 5, 6].iter().zip_clones(Zipped {}).count();
assert_eq!(iter_len, 6);
assert_eq!(ZIPPED_CLONES_COUNTER.load(Ordering::SeqCst), 5);
}
}