Skip to content

Commit 1255025

Browse files
bors[bot]vadixidav
andauthored
Merge #527
527: add .flatten_ok() r=jswrenn a=vadixidav Closes #522 Co-authored-by: Geordon Worley <[email protected]>
2 parents 9326260 + fedb59e commit 1255025

File tree

3 files changed

+268
-0
lines changed

3 files changed

+268
-0
lines changed

src/flatten_ok.rs

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
use crate::size_hint;
2+
use std::{
3+
fmt,
4+
iter::{DoubleEndedIterator, FusedIterator},
5+
};
6+
7+
pub fn flatten_ok<I, T, E>(iter: I) -> FlattenOk<I, T, E>
8+
where
9+
I: Iterator<Item = Result<T, E>>,
10+
T: IntoIterator,
11+
{
12+
FlattenOk {
13+
iter,
14+
inner_front: None,
15+
inner_back: None,
16+
}
17+
}
18+
19+
/// An iterator adaptor that flattens `Result::Ok` values and
20+
/// allows `Result::Err` values through unchanged.
21+
///
22+
/// See [`.flatten_ok()`](crate::Itertools::flatten_ok) for more information.
23+
#[must_use = "iterator adaptors are lazy and do nothing unless consumed"]
24+
pub struct FlattenOk<I, T, E>
25+
where
26+
I: Iterator<Item = Result<T, E>>,
27+
T: IntoIterator,
28+
{
29+
iter: I,
30+
inner_front: Option<T::IntoIter>,
31+
inner_back: Option<T::IntoIter>,
32+
}
33+
34+
impl<I, T, E> Iterator for FlattenOk<I, T, E>
35+
where
36+
I: Iterator<Item = Result<T, E>>,
37+
T: IntoIterator,
38+
{
39+
type Item = Result<T::Item, E>;
40+
41+
fn next(&mut self) -> Option<Self::Item> {
42+
loop {
43+
// Handle the front inner iterator.
44+
if let Some(inner) = &mut self.inner_front {
45+
if let Some(item) = inner.next() {
46+
return Some(Ok(item));
47+
} else {
48+
// This is necessary for the iterator to implement `FusedIterator`
49+
// with only the orginal iterator being fused.
50+
self.inner_front = None;
51+
}
52+
}
53+
54+
match self.iter.next() {
55+
Some(Ok(ok)) => self.inner_front = Some(ok.into_iter()),
56+
Some(Err(e)) => return Some(Err(e)),
57+
None => {
58+
// Handle the back inner iterator.
59+
if let Some(inner) = &mut self.inner_back {
60+
if let Some(item) = inner.next() {
61+
return Some(Ok(item));
62+
} else {
63+
// This is necessary for the iterator to implement `FusedIterator`
64+
// with only the orginal iterator being fused.
65+
self.inner_back = None;
66+
}
67+
} else {
68+
return None;
69+
}
70+
}
71+
}
72+
}
73+
}
74+
75+
fn size_hint(&self) -> (usize, Option<usize>) {
76+
let inner_hint = |inner: &Option<T::IntoIter>| {
77+
inner
78+
.as_ref()
79+
.map(Iterator::size_hint)
80+
.unwrap_or((0, Some(0)))
81+
};
82+
let inner_front = inner_hint(&self.inner_front);
83+
let inner_back = inner_hint(&self.inner_back);
84+
// The outer iterator `Ok` case could be (0, None) as we don't know its size_hint yet.
85+
let outer = match self.iter.size_hint() {
86+
(0, Some(0)) => (0, Some(0)),
87+
_ => (0, None),
88+
};
89+
90+
size_hint::add(size_hint::add(inner_front, inner_back), outer)
91+
}
92+
}
93+
94+
impl<I, T, E> DoubleEndedIterator for FlattenOk<I, T, E>
95+
where
96+
I: DoubleEndedIterator<Item = Result<T, E>>,
97+
T: IntoIterator,
98+
T::IntoIter: DoubleEndedIterator,
99+
{
100+
fn next_back(&mut self) -> Option<Self::Item> {
101+
loop {
102+
// Handle the back inner iterator.
103+
if let Some(inner) = &mut self.inner_back {
104+
if let Some(item) = inner.next_back() {
105+
return Some(Ok(item));
106+
} else {
107+
// This is necessary for the iterator to implement `FusedIterator`
108+
// with only the orginal iterator being fused.
109+
self.inner_back = None;
110+
}
111+
}
112+
113+
match self.iter.next_back() {
114+
Some(Ok(ok)) => self.inner_back = Some(ok.into_iter()),
115+
Some(Err(e)) => return Some(Err(e)),
116+
None => {
117+
// Handle the front inner iterator.
118+
if let Some(inner) = &mut self.inner_front {
119+
if let Some(item) = inner.next_back() {
120+
return Some(Ok(item));
121+
} else {
122+
// This is necessary for the iterator to implement `FusedIterator`
123+
// with only the orginal iterator being fused.
124+
self.inner_front = None;
125+
}
126+
} else {
127+
return None;
128+
}
129+
}
130+
}
131+
}
132+
}
133+
}
134+
135+
impl<I, T, E> Clone for FlattenOk<I, T, E>
136+
where
137+
I: Iterator<Item = Result<T, E>> + Clone,
138+
T: IntoIterator,
139+
T::IntoIter: Clone,
140+
{
141+
#[inline]
142+
clone_fields!(iter, inner_front, inner_back);
143+
}
144+
145+
impl<I, T, E> fmt::Debug for FlattenOk<I, T, E>
146+
where
147+
I: Iterator<Item = Result<T, E>> + fmt::Debug,
148+
T: IntoIterator,
149+
T::IntoIter: fmt::Debug,
150+
{
151+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
152+
f.debug_struct("FlattenOk")
153+
.field("iter", &self.iter)
154+
.field("inner_front", &self.inner_front)
155+
.field("inner_back", &self.inner_back)
156+
.finish()
157+
}
158+
}
159+
160+
/// Only the iterator being flattened needs to implement [`FusedIterator`].
161+
impl<I, T, E> FusedIterator for FlattenOk<I, T, E>
162+
where
163+
I: FusedIterator<Item = Result<T, E>>,
164+
T: IntoIterator,
165+
{
166+
}

src/lib.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ pub mod structs {
119119
pub use crate::cons_tuples_impl::ConsTuples;
120120
pub use crate::exactly_one_err::ExactlyOneError;
121121
pub use crate::format::{Format, FormatWith};
122+
pub use crate::flatten_ok::FlattenOk;
122123
#[cfg(feature = "use_std")]
123124
pub use crate::grouping_map::{GroupingMap, GroupingMapBy};
124125
#[cfg(feature = "use_alloc")]
@@ -194,6 +195,7 @@ mod combinations;
194195
mod combinations_with_replacement;
195196
mod exactly_one_err;
196197
mod diff;
198+
mod flatten_ok;
197199
mod format;
198200
#[cfg(feature = "use_std")]
199201
mod grouping_map;
@@ -899,6 +901,30 @@ pub trait Itertools : Iterator {
899901
adaptors::filter_map_ok(self, f)
900902
}
901903

904+
/// Return an iterator adaptor that flattens every `Result::Ok` value into
905+
/// a series of `Result::Ok` values. `Result::Err` values are unchanged.
906+
///
907+
/// This is useful when you have some common error type for your crate and
908+
/// need to propogate it upwards, but the `Result::Ok` case needs to be flattened.
909+
///
910+
/// ```
911+
/// use itertools::Itertools;
912+
///
913+
/// let input = vec![Ok(0..2), Err(false), Ok(2..4)];
914+
/// let it = input.iter().cloned().flatten_ok();
915+
/// itertools::assert_equal(it.clone(), vec![Ok(0), Ok(1), Err(false), Ok(2), Ok(3)]);
916+
///
917+
/// // This can also be used to propogate errors when collecting.
918+
/// let output_result: Result<Vec<i32>, bool> = it.collect();
919+
/// assert_eq!(output_result, Err(false));
920+
/// ```
921+
fn flatten_ok<T, E>(self) -> FlattenOk<Self, T, E>
922+
where Self: Iterator<Item = Result<T, E>> + Sized,
923+
T: IntoIterator
924+
{
925+
flatten_ok::flatten_ok(self)
926+
}
927+
902928
/// Return an iterator adaptor that merges the two base iterators in
903929
/// ascending order. If both base iterators are sorted (ascending), the
904930
/// result is sorted.

tests/flatten_ok.rs

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
use itertools::{assert_equal, Itertools};
2+
use std::{ops::Range, vec::IntoIter};
3+
4+
fn mix_data() -> IntoIter<Result<Range<i32>, bool>> {
5+
vec![Ok(0..2), Err(false), Ok(2..4), Err(true), Ok(4..6)].into_iter()
6+
}
7+
8+
fn ok_data() -> IntoIter<Result<Range<i32>, bool>> {
9+
vec![Ok(0..2), Ok(2..4), Ok(4..6)].into_iter()
10+
}
11+
12+
#[test]
13+
fn flatten_ok_mixed_expected_forward() {
14+
assert_equal(
15+
mix_data().flatten_ok(),
16+
vec![
17+
Ok(0),
18+
Ok(1),
19+
Err(false),
20+
Ok(2),
21+
Ok(3),
22+
Err(true),
23+
Ok(4),
24+
Ok(5),
25+
],
26+
);
27+
}
28+
29+
#[test]
30+
fn flatten_ok_mixed_expected_reverse() {
31+
assert_equal(
32+
mix_data().flatten_ok().rev(),
33+
vec![
34+
Ok(5),
35+
Ok(4),
36+
Err(true),
37+
Ok(3),
38+
Ok(2),
39+
Err(false),
40+
Ok(1),
41+
Ok(0),
42+
],
43+
);
44+
}
45+
46+
#[test]
47+
fn flatten_ok_collect_mixed_forward() {
48+
assert_eq!(
49+
mix_data().flatten_ok().collect::<Result<Vec<_>, _>>(),
50+
Err(false)
51+
);
52+
}
53+
54+
#[test]
55+
fn flatten_ok_collect_mixed_reverse() {
56+
assert_eq!(
57+
mix_data().flatten_ok().rev().collect::<Result<Vec<_>, _>>(),
58+
Err(true)
59+
);
60+
}
61+
62+
#[test]
63+
fn flatten_ok_collect_ok_forward() {
64+
assert_eq!(
65+
ok_data().flatten_ok().collect::<Result<Vec<_>, _>>(),
66+
Ok((0..6).collect())
67+
);
68+
}
69+
70+
#[test]
71+
fn flatten_ok_collect_ok_reverse() {
72+
assert_eq!(
73+
ok_data().flatten_ok().rev().collect::<Result<Vec<_>, _>>(),
74+
Ok((0..6).rev().collect())
75+
);
76+
}

0 commit comments

Comments
 (0)