Skip to content

Commit

Permalink
Add zip_clones, zips an iterator with clones of a value
Browse files Browse the repository at this point in the history
Similar to `iter.zip(repeat_n(val, n))' but does not require knowing n
  • Loading branch information
barakugav committed Sep 1, 2024
1 parent 91f9618 commit f04a047
Show file tree
Hide file tree
Showing 2 changed files with 114 additions and 0 deletions.
37 changes: 37 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,41 @@ 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`
/// unnecessary using the trivial following code:
/// ```rust
/// let it = [0, 1, 2, 3, 4].into_iter();
/// let zipped = "expensive-to-clone".to_string();
/// for a in it {
/// let b = zipped.clone();
/// // do something that consumes the expensive zipped value
/// }
/// ```
/// Instead, you can use `zip_clones`:
/// ```rust
/// use itertools::Itertools;
/// let it = [0, 1, 2, 3, 4].into_iter();
/// let zipped = "expensive-to-clone".to_string();
/// for (a, b) in it.zip_clones(zipped) {
/// // do something that consumes the expensive zipped value
/// }
/// ```
///
/// 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 @@
/// 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: I,
next: Option<I::Item>,
zipped: Option<T>,
}

/// Zips an iterator with clones of a value.
///
/// [`IntoIterator`] enabled version of [`Itertools::zip_clones`](crate::Itertools::zip_clones).
///
/// ```
/// use itertools::zip_clones;
///
/// let data = [1, 2, 3, 4, 5];
/// let zipped = "expensive-to-clone".to_string();
/// for (a, b) in zip_clones(&data, zipped) {
/// // do something that consumes the expensive zipped value
/// }
/// ```
pub fn zip_clones<I, T>(i: I, zipped: T) -> ZipClones<I::IntoIter, T>
where
I: IntoIterator,
T: Clone,
{
let mut iter = i.into_iter();
let next = iter.next();
ZipClones {
iter,
next,
zipped: 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()?;
self.next = self.iter.next();
let zipped = if self.next.is_some() {
self.zipped.clone()
} else {
self.zipped.take()
};
// Safety: the zipped field is only self.next is none
let zipped = unsafe { zipped.unwrap_unchecked() };
Some((cur, zipped))
}
}

#[cfg(test)]
mod tests {
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);
}
}

0 comments on commit f04a047

Please sign in to comment.