Skip to content

Commit d68008d

Browse files
bors[bot]Xaeroxe
andcommitted
Merge #310
310: Add exactly_one function r=bluss a=Xaeroxe Adds a small function I found uses for personally. Can't be added to the core library because in the error case it allocates a vec and stores data in it. Co-authored-by: Jacob Kiesel <[email protected]>
2 parents cd0602a + 094b416 commit d68008d

File tree

2 files changed

+99
-3
lines changed

2 files changed

+99
-3
lines changed

src/exactly_one_err.rs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
use std::iter::ExactSizeIterator;
2+
3+
use size_hint;
4+
5+
/// Iterator returned for the error case of `IterTools::exactly_one()`
6+
/// This iterator yields exactly the same elements as the input iterator.
7+
///
8+
/// During the execution of exactly_one the iterator must be mutated. This wrapper
9+
/// effectively "restores" the state of the input iterator when it's handed back.
10+
///
11+
/// This is very similar to PutBackN except this iterator only supports 0-2 elements and does not
12+
/// use a `Vec`.
13+
#[derive(Debug, Clone)]
14+
pub struct ExactlyOneError<I>
15+
where
16+
I: Iterator,
17+
{
18+
first_two: (Option<I::Item>, Option<I::Item>),
19+
inner: I,
20+
}
21+
22+
impl<I> ExactlyOneError<I>
23+
where
24+
I: Iterator,
25+
{
26+
/// Creates a new `ExactlyOneErr` iterator.
27+
pub fn new(first_two: (Option<I::Item>, Option<I::Item>), inner: I) -> Self {
28+
Self { first_two, inner }
29+
}
30+
}
31+
32+
impl<I> Iterator for ExactlyOneError<I>
33+
where
34+
I: Iterator,
35+
{
36+
type Item = I::Item;
37+
38+
fn next(&mut self) -> Option<Self::Item> {
39+
self.first_two
40+
.0
41+
.take()
42+
.or_else(|| self.first_two.1.take())
43+
.or_else(|| self.inner.next())
44+
}
45+
46+
fn size_hint(&self) -> (usize, Option<usize>) {
47+
let mut additional_len = 0;
48+
if self.first_two.0.is_some() {
49+
additional_len += 1;
50+
}
51+
if self.first_two.1.is_some() {
52+
additional_len += 1;
53+
}
54+
size_hint::add_scalar(self.inner.size_hint(), additional_len)
55+
}
56+
}
57+
58+
impl<I> ExactSizeIterator for ExactlyOneError<I> where I: ExactSizeIterator {}

src/lib.rs

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ pub mod structs {
102102
#[cfg(feature = "use_std")]
103103
pub use combinations::Combinations;
104104
pub use cons_tuples_impl::ConsTuples;
105+
pub use exactly_one_err::ExactlyOneError;
105106
pub use format::{Format, FormatWith};
106107
#[cfg(feature = "use_std")]
107108
pub use groupbylazy::{IntoChunks, Chunk, Chunks, GroupBy, Group, Groups};
@@ -158,6 +159,7 @@ mod concat_impl;
158159
mod cons_tuples_impl;
159160
#[cfg(feature = "use_std")]
160161
mod combinations;
162+
mod exactly_one_err;
161163
mod diff;
162164
mod format;
163165
#[cfg(feature = "use_std")]
@@ -1943,13 +1945,13 @@ pub trait Itertools : Iterator {
19431945

19441946
/// Return a `HashMap` of keys mapped to `Vec`s of values. Keys and values
19451947
/// are taken from `(Key, Value)` tuple pairs yielded by the input iterator.
1946-
///
1948+
///
19471949
/// ```
19481950
/// use itertools::Itertools;
1949-
///
1951+
///
19501952
/// let data = vec![(0, 10), (2, 12), (3, 13), (0, 20), (3, 33), (2, 42)];
19511953
/// let lookup = data.into_iter().into_group_map();
1952-
///
1954+
///
19531955
/// assert_eq!(lookup[&0], vec![10, 20]);
19541956
/// assert_eq!(lookup.get(&1), None);
19551957
/// assert_eq!(lookup[&2], vec![12, 42]);
@@ -2038,6 +2040,42 @@ pub trait Itertools : Iterator {
20382040
|x, y, _, _| Ordering::Less == compare(x, y)
20392041
)
20402042
}
2043+
2044+
/// If the iterator yields exactly one element, that element will be returned, otherwise
2045+
/// an error will be returned containing an iterator that has the same output as the input
2046+
/// iterator.
2047+
///
2048+
/// This provides an additional layer of validation over just calling `Iterator::next()`.
2049+
/// If your assumption that there should only be one element yielded is false this provides
2050+
/// the opportunity to detect and handle that, preventing errors at a distance.
2051+
///
2052+
/// # Examples
2053+
/// ```
2054+
/// use itertools::Itertools;
2055+
///
2056+
/// assert_eq!((0..10).filter(|&x| x == 2).exactly_one().unwrap(), 2);
2057+
/// assert!((0..10).filter(|&x| x > 1 && x < 4).exactly_one().unwrap_err().eq(2..4));
2058+
/// assert!((0..10).filter(|&x| x > 1 && x < 5).exactly_one().unwrap_err().eq(2..5));
2059+
/// assert!((0..10).filter(|&x| false).exactly_one().unwrap_err().eq(0..0));
2060+
/// ```
2061+
fn exactly_one(mut self) -> Result<Self::Item, ExactlyOneError<Self>>
2062+
where
2063+
Self: Sized,
2064+
{
2065+
match self.next() {
2066+
Some(first) => {
2067+
match self.next() {
2068+
Some(second) => {
2069+
Err(ExactlyOneError::new((Some(first), Some(second)), self))
2070+
}
2071+
None => {
2072+
Ok(first)
2073+
}
2074+
}
2075+
}
2076+
None => Err(ExactlyOneError::new((None, None), self)),
2077+
}
2078+
}
20412079
}
20422080

20432081
impl<T: ?Sized> Itertools for T where T: Iterator { }

0 commit comments

Comments
 (0)