diff --git a/src/lib.rs b/src/lib.rs index 4db8f5d40..c066a8852 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3145,6 +3145,42 @@ pub trait Itertools : Iterator { } } + /// If the iterator yields no elements, Ok(None) will be returned. If the iterator yields + /// exactly one element, that element will be returned, otherwise an error will be returned + /// containing an iterator that has the same output as the input iterator. + /// + /// This provides an additional layer of validation over just calling `Iterator::next()`. + /// If your assumption that there should be at most one element yielded is false this provides + /// the opportunity to detect and handle that, preventing errors at a distance. + /// + /// # Examples + /// ``` + /// use itertools::Itertools; + /// + /// assert_eq!((0..10).filter(|&x| x == 2).at_most_one().unwrap(), Some(2)); + /// assert!((0..10).filter(|&x| x > 1 && x < 4).at_most_one().unwrap_err().eq(2..4)); + /// assert!((0..10).filter(|&x| x > 1 && x < 5).at_most_one().unwrap_err().eq(2..5)); + /// assert_eq!((0..10).filter(|&_| false).at_most_one().unwrap(), None); + /// ``` + fn at_most_one(mut self) -> Result, ExactlyOneError> + where + Self: Sized, + { + match self.next() { + Some(first) => { + match self.next() { + Some(second) => { + Err(ExactlyOneError::new(Some(Either::Left([first, second])), self)) + } + None => { + Ok(Some(first)) + } + } + } + None => Ok(None), + } + } + /// An iterator adaptor that allows the user to peek at multiple `.next()` /// values without advancing the base iterator. /// diff --git a/tests/quick.rs b/tests/quick.rs index b4ae576af..7769cb432 100644 --- a/tests/quick.rs +++ b/tests/quick.rs @@ -1203,6 +1203,17 @@ quickcheck! { } } +quickcheck! { + fn at_most_one_i32(a: Vec) -> TestResult { + let ret = a.iter().cloned().at_most_one(); + match a.len() { + 0 => TestResult::from_bool(ret.unwrap() == None), + 1 => TestResult::from_bool(ret.unwrap() == Some(a[0])), + _ => TestResult::from_bool(ret.unwrap_err().eq(a.iter().cloned())), + } + } +} + quickcheck! { fn consistent_grouping_map_with_by(a: Vec, modulo: u8) -> () { let modulo = if modulo == 0 { 1 } else { modulo }; // Avoid `% 0` diff --git a/tests/test_core.rs b/tests/test_core.rs index 5861653da..16cf06f25 100644 --- a/tests/test_core.rs +++ b/tests/test_core.rs @@ -253,6 +253,14 @@ fn exactly_one() { assert!((0..10).filter(|&_| false).exactly_one().unwrap_err().eq(0..0)); } +#[test] +fn at_most_one() { + assert_eq!((0..10).filter(|&x| x == 2).at_most_one().unwrap(), Some(2)); + assert!((0..10).filter(|&x| x > 1 && x < 4).at_most_one().unwrap_err().eq(2..4)); + assert!((0..10).filter(|&x| x > 1 && x < 5).at_most_one().unwrap_err().eq(2..5)); + assert_eq!((0..10).filter(|&_| false).at_most_one().unwrap(), None); +} + #[test] fn sum1() { let v: &[i32] = &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];