Skip to content

Commit 7ed824e

Browse files
committed
Add Iterator::intersperse_with
1 parent 9775ffe commit 7ed824e

File tree

5 files changed

+192
-25
lines changed

5 files changed

+192
-25
lines changed

library/core/src/iter/adapters/intersperse.rs

+133-21
Original file line numberDiff line numberDiff line change
@@ -40,37 +40,149 @@ where
4040
}
4141
}
4242

43-
fn fold<B, F>(mut self, init: B, mut f: F) -> B
43+
fn fold<B, F>(self, init: B, f: F) -> B
4444
where
4545
Self: Sized,
4646
F: FnMut(B, Self::Item) -> B,
4747
{
48-
let mut accum = init;
48+
let separator = self.separator;
49+
intersperse_fold(self.iter, init, f, move || separator.clone(), self.needs_sep)
50+
}
51+
52+
fn size_hint(&self) -> (usize, Option<usize>) {
53+
intersperse_size_hint(&self.iter, self.needs_sep)
54+
}
55+
}
56+
57+
/// An iterator adapter that places a separator between all elements.
58+
#[unstable(feature = "iter_intersperse", reason = "recently added", issue = "79524")]
59+
pub struct IntersperseWith<I, G>
60+
where
61+
I: Iterator,
62+
{
63+
separator: G,
64+
iter: Peekable<I>,
65+
needs_sep: bool,
66+
}
67+
68+
// FIXME This manual implementation is needed as #[derive] misplaces trait bounds,
69+
// requiring <I as Iterator>::Item to be Debug on the struct-definition, which is
70+
// not what we want.
71+
#[unstable(feature = "iter_intersperse", reason = "recently added", issue = "79524")]
72+
impl<I, G> crate::fmt::Debug for IntersperseWith<I, G>
73+
where
74+
I: Iterator + crate::fmt::Debug,
75+
I::Item: crate::fmt::Debug,
76+
G: crate::fmt::Debug,
77+
{
78+
fn fmt(&self, f: &mut crate::fmt::Formatter<'_>) -> crate::fmt::Result {
79+
f.debug_struct("IntersperseWith")
80+
.field("separator", &self.separator)
81+
.field("iter", &self.iter)
82+
.field("needs_sep", &self.needs_sep)
83+
.finish()
84+
}
85+
}
4986

50-
// Use `peek()` first to avoid calling `next()` on an empty iterator.
51-
if !self.needs_sep || self.iter.peek().is_some() {
52-
if let Some(x) = self.iter.next() {
53-
accum = f(accum, x);
54-
}
87+
// FIXME This manual implementation is needed as #[derive] misplaces trait bounds,
88+
// requiring <I as Iterator>::Item to be Clone on the struct-definition, which is
89+
// not what we want.
90+
#[unstable(feature = "iter_intersperse", reason = "recently added", issue = "79524")]
91+
impl<I, G> crate::clone::Clone for IntersperseWith<I, G>
92+
where
93+
I: Iterator + crate::clone::Clone,
94+
I::Item: crate::clone::Clone,
95+
G: Clone,
96+
{
97+
fn clone(&self) -> Self {
98+
IntersperseWith {
99+
separator: self.separator.clone(),
100+
iter: self.iter.clone(),
101+
needs_sep: self.needs_sep.clone(),
55102
}
103+
}
104+
}
56105

57-
let element = &self.separator;
106+
impl<I, G> IntersperseWith<I, G>
107+
where
108+
I: Iterator,
109+
G: FnMut() -> I::Item,
110+
{
111+
pub(in crate::iter) fn new(iter: I, separator: G) -> Self {
112+
Self { iter: iter.peekable(), separator, needs_sep: false }
113+
}
114+
}
58115

59-
self.iter.fold(accum, |mut accum, x| {
60-
accum = f(accum, element.clone());
61-
accum = f(accum, x);
62-
accum
63-
})
116+
#[unstable(feature = "iter_intersperse", reason = "recently added", issue = "79524")]
117+
impl<I, G> Iterator for IntersperseWith<I, G>
118+
where
119+
I: Iterator,
120+
G: FnMut() -> I::Item,
121+
{
122+
type Item = I::Item;
123+
124+
#[inline]
125+
fn next(&mut self) -> Option<I::Item> {
126+
if self.needs_sep && self.iter.peek().is_some() {
127+
self.needs_sep = false;
128+
Some((self.separator)())
129+
} else {
130+
self.needs_sep = true;
131+
self.iter.next()
132+
}
133+
}
134+
135+
fn fold<B, F>(self, init: B, f: F) -> B
136+
where
137+
Self: Sized,
138+
F: FnMut(B, Self::Item) -> B,
139+
{
140+
intersperse_fold(self.iter, init, f, self.separator, self.needs_sep)
64141
}
65142

66143
fn size_hint(&self) -> (usize, Option<usize>) {
67-
let (lo, hi) = self.iter.size_hint();
68-
let next_is_elem = !self.needs_sep;
69-
let lo = lo.saturating_sub(next_is_elem as usize).saturating_add(lo);
70-
let hi = match hi {
71-
Some(hi) => hi.saturating_sub(next_is_elem as usize).checked_add(hi),
72-
None => None,
73-
};
74-
(lo, hi)
144+
intersperse_size_hint(&self.iter, self.needs_sep)
75145
}
76146
}
147+
148+
fn intersperse_size_hint<I>(iter: &I, needs_sep: bool) -> (usize, Option<usize>)
149+
where
150+
I: Iterator,
151+
{
152+
let (lo, hi) = iter.size_hint();
153+
let next_is_elem = !needs_sep;
154+
let lo = lo.saturating_sub(next_is_elem as usize).saturating_add(lo);
155+
let hi = match hi {
156+
Some(hi) => hi.saturating_sub(next_is_elem as usize).checked_add(hi),
157+
None => None,
158+
};
159+
(lo, hi)
160+
}
161+
162+
fn intersperse_fold<I, B, F, G>(
163+
mut iter: Peekable<I>,
164+
init: B,
165+
mut f: F,
166+
mut separator: G,
167+
needs_sep: bool,
168+
) -> B
169+
where
170+
I: Iterator,
171+
F: FnMut(B, I::Item) -> B,
172+
G: FnMut() -> I::Item,
173+
{
174+
let mut accum = init;
175+
176+
// Use `peek()` first to avoid calling `next()` on an empty iterator.
177+
if !needs_sep || iter.peek().is_some() {
178+
if let Some(x) = iter.next() {
179+
accum = f(accum, x);
180+
}
181+
}
182+
183+
iter.fold(accum, |mut accum, x| {
184+
accum = f(accum, separator());
185+
accum = f(accum, x);
186+
accum
187+
})
188+
}

library/core/src/iter/adapters/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ pub use self::flatten::Flatten;
4343
pub use self::copied::Copied;
4444

4545
#[unstable(feature = "iter_intersperse", reason = "recently added", issue = "79524")]
46-
pub use self::intersperse::Intersperse;
46+
pub use self::intersperse::{Intersperse, IntersperseWith};
4747

4848
#[unstable(feature = "iter_map_while", reason = "recently added", issue = "68537")]
4949
pub use self::map_while::MapWhile;

library/core/src/iter/mod.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -395,8 +395,6 @@ pub use self::adapters::Cloned;
395395
pub use self::adapters::Copied;
396396
#[stable(feature = "iterator_flatten", since = "1.29.0")]
397397
pub use self::adapters::Flatten;
398-
#[unstable(feature = "iter_intersperse", reason = "recently added", issue = "79524")]
399-
pub use self::adapters::Intersperse;
400398
#[unstable(feature = "iter_map_while", reason = "recently added", issue = "68537")]
401399
pub use self::adapters::MapWhile;
402400
#[unstable(feature = "inplace_iteration", issue = "none")]
@@ -410,6 +408,8 @@ pub use self::adapters::{
410408
Chain, Cycle, Enumerate, Filter, FilterMap, FlatMap, Fuse, Inspect, Map, Peekable, Rev, Scan,
411409
Skip, SkipWhile, Take, TakeWhile, Zip,
412410
};
411+
#[unstable(feature = "iter_intersperse", reason = "recently added", issue = "79524")]
412+
pub use self::adapters::{Intersperse, IntersperseWith};
413413

414414
pub(crate) use self::adapters::process_results;
415415

library/core/src/iter/traits/iterator.rs

+26-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use crate::ops::{Add, ControlFlow, Try};
88
use super::super::TrustedRandomAccess;
99
use super::super::{Chain, Cloned, Copied, Cycle, Enumerate, Filter, FilterMap, Fuse};
1010
use super::super::{FlatMap, Flatten};
11-
use super::super::{FromIterator, Intersperse, Product, Sum, Zip};
11+
use super::super::{FromIterator, Intersperse, IntersperseWith, Product, Sum, Zip};
1212
use super::super::{
1313
Inspect, Map, MapWhile, Peekable, Rev, Scan, Skip, SkipWhile, StepBy, Take, TakeWhile,
1414
};
@@ -591,6 +591,31 @@ pub trait Iterator {
591591
Intersperse::new(self, separator)
592592
}
593593

594+
/// Places an element generated by `separator` between all elements.
595+
///
596+
/// # Examples
597+
///
598+
/// Basic usage:
599+
///
600+
/// ```
601+
/// #![feature(iter_intersperse)]
602+
///
603+
/// let src = ["Hello", "to", "all", "people"].iter().copied();
604+
/// let mut separator = [" ❤️ ", " 😀 "].iter().copied().cycle();
605+
///
606+
/// let result = src.intersperse_with(|| separator.next().unwrap()).collect::<String>();
607+
/// assert_eq!(result, "Hello ❤️ to 😀 all ❤️ people");
608+
/// ```
609+
#[inline]
610+
#[unstable(feature = "iter_intersperse", reason = "recently added", issue = "79524")]
611+
fn intersperse_with<G>(self, separator: G) -> IntersperseWith<Self, G>
612+
where
613+
Self: Sized,
614+
G: FnMut() -> Self::Item,
615+
{
616+
IntersperseWith::new(self, separator)
617+
}
618+
594619
/// Takes a closure and creates an iterator which calls that closure on each
595620
/// element.
596621
///

library/core/tests/iter.rs

+30
Original file line numberDiff line numberDiff line change
@@ -3508,6 +3508,12 @@ pub fn extend_for_unit() {
35083508

35093509
#[test]
35103510
fn test_intersperse() {
3511+
let v = std::iter::empty().intersperse(0u32).collect::<Vec<_>>();
3512+
assert_eq!(v, vec![]);
3513+
3514+
let v = std::iter::once(1).intersperse(0).collect::<Vec<_>>();
3515+
assert_eq!(v, vec![1]);
3516+
35113517
let xs = ["a", "", "b", "c"];
35123518
let v: Vec<&str> = xs.iter().map(|x| x.clone()).intersperse(", ").collect();
35133519
let text: String = v.concat();
@@ -3520,6 +3526,9 @@ fn test_intersperse() {
35203526

35213527
#[test]
35223528
fn test_intersperse_size_hint() {
3529+
let iter = std::iter::empty::<i32>().intersperse(0);
3530+
assert_eq!(iter.size_hint(), (0, Some(0)));
3531+
35233532
let xs = ["a", "", "b", "c"];
35243533
let mut iter = xs.iter().map(|x| x.clone()).intersperse(", ");
35253534
assert_eq!(iter.size_hint(), (7, Some(7)));
@@ -3587,3 +3596,24 @@ fn test_try_fold_specialization_intersperse_err() {
35873596
iter.try_for_each(|item| if item == "b" { None } else { Some(()) });
35883597
assert_eq!(iter.next(), None);
35893598
}
3599+
3600+
#[test]
3601+
fn test_intersperse_with() {
3602+
#[derive(PartialEq, Debug)]
3603+
struct NotClone {
3604+
u: u32,
3605+
}
3606+
let r = vec![NotClone { u: 0 }, NotClone { u: 1 }]
3607+
.into_iter()
3608+
.intersperse_with(|| NotClone { u: 2 })
3609+
.collect::<Vec<_>>();
3610+
assert_eq!(r, vec![NotClone { u: 0 }, NotClone { u: 2 }, NotClone { u: 1 }]);
3611+
3612+
let mut ctr = 100;
3613+
let separator = || {
3614+
ctr *= 2;
3615+
ctr
3616+
};
3617+
let r = (0..3).intersperse_with(separator).collect::<Vec<_>>();
3618+
assert_eq!(r, vec![0, 200, 1, 400, 2]);
3619+
}

0 commit comments

Comments
 (0)