Skip to content

Commit f04a047

Browse files
committed
Add zip_clones, zips an iterator with clones of a value
Similar to `iter.zip(repeat_n(val, n))' but does not require knowing n
1 parent 91f9618 commit f04a047

File tree

2 files changed

+114
-0
lines changed

2 files changed

+114
-0
lines changed

src/lib.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ pub mod structs {
142142
#[cfg(feature = "use_std")]
143143
pub use crate::unique_impl::{Unique, UniqueBy};
144144
pub use crate::with_position::WithPosition;
145+
pub use crate::zip_clones::ZipClones;
145146
pub use crate::zip_eq_impl::ZipEq;
146147
pub use crate::zip_longest::ZipLongest;
147148
pub use crate::ziptuple::Zip;
@@ -233,6 +234,7 @@ mod tuple_impl;
233234
mod unique_impl;
234235
mod unziptuple;
235236
mod with_position;
237+
mod zip_clones;
236238
mod zip_eq_impl;
237239
mod zip_longest;
238240
mod ziptuple;
@@ -617,6 +619,41 @@ pub trait Itertools: Iterator {
617619
zip_eq(self, other)
618620
}
619621

622+
/// Create an iterator which iterates over this iterator paired with clones of a given value.
623+
///
624+
/// If the iterator has `n` elements, the zipped value will be cloned `n-1` times. This function
625+
/// is useful when the zipped value is expensive to clone and you want to avoid cloning it `n`
626+
/// unnecessary using the trivial following code:
627+
/// ```rust
628+
/// let it = [0, 1, 2, 3, 4].into_iter();
629+
/// let zipped = "expensive-to-clone".to_string();
630+
/// for a in it {
631+
/// let b = zipped.clone();
632+
/// // do something that consumes the expensive zipped value
633+
/// }
634+
/// ```
635+
/// Instead, you can use `zip_clones`:
636+
/// ```rust
637+
/// use itertools::Itertools;
638+
/// let it = [0, 1, 2, 3, 4].into_iter();
639+
/// let zipped = "expensive-to-clone".to_string();
640+
/// for (a, b) in it.zip_clones(zipped) {
641+
/// // do something that consumes the expensive zipped value
642+
/// }
643+
/// ```
644+
///
645+
/// The [`repeat_n()`](crate::repeat_n) function can be used to create from a zipped value
646+
/// an iterator that also clones the value `n-1` times, but it require to know the number of
647+
/// elements in the iterator in advance.
648+
#[inline]
649+
fn zip_clones<T>(self, zipped: T) -> ZipClones<Self, T>
650+
where
651+
Self: Sized,
652+
T: Clone,
653+
{
654+
zip_clones::zip_clones(self, zipped)
655+
}
656+
620657
/// A “meta iterator adaptor”. Its closure receives a reference to the
621658
/// iterator and may pick off as many elements as it likes, to produce the
622659
/// next iterator element.

src/zip_clones.rs

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/// An iterator which iterate over an iterator and clones of a value.
2+
///
3+
///
4+
/// See [`.zip_clones()`](crate::Itertools::zip_clones) for more information.
5+
pub struct ZipClones<I, T>
6+
where
7+
I: Iterator,
8+
{
9+
iter: I,
10+
next: Option<I::Item>,
11+
zipped: Option<T>,
12+
}
13+
14+
/// Zips an iterator with clones of a value.
15+
///
16+
/// [`IntoIterator`] enabled version of [`Itertools::zip_clones`](crate::Itertools::zip_clones).
17+
///
18+
/// ```
19+
/// use itertools::zip_clones;
20+
///
21+
/// let data = [1, 2, 3, 4, 5];
22+
/// let zipped = "expensive-to-clone".to_string();
23+
/// for (a, b) in zip_clones(&data, zipped) {
24+
/// // do something that consumes the expensive zipped value
25+
/// }
26+
/// ```
27+
pub fn zip_clones<I, T>(i: I, zipped: T) -> ZipClones<I::IntoIter, T>
28+
where
29+
I: IntoIterator,
30+
T: Clone,
31+
{
32+
let mut iter = i.into_iter();
33+
let next = iter.next();
34+
ZipClones {
35+
iter,
36+
next,
37+
zipped: Some(zipped),
38+
}
39+
}
40+
41+
impl<I: Iterator, T: Clone> Iterator for ZipClones<I, T> {
42+
type Item = (I::Item, T);
43+
fn next(&mut self) -> Option<Self::Item> {
44+
let cur = self.next.take()?;
45+
self.next = self.iter.next();
46+
let zipped = if self.next.is_some() {
47+
self.zipped.clone()
48+
} else {
49+
self.zipped.take()
50+
};
51+
// Safety: the zipped field is only self.next is none
52+
let zipped = unsafe { zipped.unwrap_unchecked() };
53+
Some((cur, zipped))
54+
}
55+
}
56+
57+
#[cfg(test)]
58+
mod tests {
59+
use std::sync::atomic::{AtomicUsize, Ordering};
60+
61+
#[test]
62+
fn test_zip_clones() {
63+
static ZIPPED_CLONES_COUNTER: AtomicUsize = AtomicUsize::new(0);
64+
struct Zipped {}
65+
impl Clone for Zipped {
66+
fn clone(&self) -> Self {
67+
ZIPPED_CLONES_COUNTER.fetch_add(1, Ordering::SeqCst);
68+
Zipped {}
69+
}
70+
}
71+
72+
ZIPPED_CLONES_COUNTER.store(0, Ordering::SeqCst);
73+
let iter_len = [1, 2, 3, 4, 5, 6].iter().zip_clones(Zipped {}).count();
74+
assert_eq!(iter_len, 6);
75+
assert_eq!(ZIPPED_CLONES_COUNTER.load(Ordering::SeqCst), 5);
76+
}
77+
}

0 commit comments

Comments
 (0)