Repository: rust-itertools/itertools
Pull request #300 (2018-08-15 17:39:08)

Add Itertools::{sum1, product1}
This implements `sum1` and `product1` consumers that are like `sum` and `product` except they produce an `Option<T>` with `Some` for nonempty iters and `None` otherwise, just like `fold1`.
That allows one to distinguish between an empty iter and iters that sum/multiply to their neutral element.

jswrenn: Thanks for the contribution! Could you rebase this on master?

Patch:
diff --git a/src/lib.rs b/src/lib.rs
index 5f3eb72..d7c7960 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -56,7 +56,7 @@ pub use either::Either;
 
 #[cfg(feature = "use_std")]
 use std::collections::HashMap;
-use std::iter::{IntoIterator};
+use std::iter::{IntoIterator, once};
 use std::cmp::Ordering;
 use std::fmt;
 #[cfg(feature = "use_std")]
@@ -1928,6 +1928,64 @@ pub trait Itertools : Iterator {
         FoldWhile::Continue(acc)
     }
 
+    /// Iterate over the entire iterator and add all the elements.
+    ///
+    /// An empty iterator returns `None`, otherwise `Some(sum)`.
+    ///
+    /// # Panics
+    ///
+    /// When calling `sum1()` and a primitive integer type is being returned, this
+    /// method will panic if the computation overflows and debug assertions are
+    /// enabled.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// use itertools::Itertools;
+    ///
+    /// let empty_sum = (1..1).sum1::<i32>();
+    /// assert_eq!(empty_sum, None);
+    ///
+    /// let nonempty_sum = (1..11).sum1::<i32>();
+    /// assert_eq!(nonempty_sum, Some(55));
+    /// ```
+    fn sum1<S>(mut self) -> Option<S>
+        where Self: Sized,
+              S: std::iter::Sum<Self::Item>,
+    {
+        self.next()
+            .map(|first| once(first).chain(self).sum())
+    }
+
+    /// Iterate over the entire iterator and multiply all the elements.
+    ///
+    /// An empty iterator returns `None`, otherwise `Some(product)`.
+    ///
+    /// # Panics
+    ///
+    /// When calling `product1()` and a primitive integer type is being returned,
+    /// method will panic if the computation overflows and debug assertions are
+    /// enabled.
+    ///
+    /// # Examples
+    /// ```
+    /// use itertools::Itertools;
+    ///
+    /// let empty_product = (1..1).product1::<i32>();
+    /// assert_eq!(empty_product, None);
+    ///
+    /// let nonempty_product = (1..11).product1::<i32>();
+    /// assert_eq!(nonempty_product, Some(3628800));
+    /// ```
+    fn product1<P>(mut self) -> Option<P>
+        where Self: Sized,
+              P: std::iter::Product<Self::Item>,
+    {
+        self.next()
+            .map(|first| once(first).chain(self).product())
+    }
+
+
     /// Sort all iterator elements into a new iterator in ascending order.
     ///
     /// **Note:** This consumes the entire iterator, uses the


Test patch:
diff --git a/tests/test_core.rs b/tests/test_core.rs
index 7007217..b1e03cf 100644
--- a/tests/test_core.rs
+++ b/tests/test_core.rs
@@ -252,3 +252,21 @@ fn exactly_one() {
     assert!((0..10).filter(|&x| x > 1 && x < 5).exactly_one().unwrap_err().eq(2..5));
     assert!((0..10).filter(|&_| false).exactly_one().unwrap_err().eq(0..0));
 }
+
+#[test]
+fn sum1() {
+    let v: &[i32] = &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
+    assert_eq!(v[..0].iter().cloned().sum1::<i32>(), None);
+    assert_eq!(v[1..2].iter().cloned().sum1::<i32>(), Some(1));
+    assert_eq!(v[1..3].iter().cloned().sum1::<i32>(), Some(3));
+    assert_eq!(v.iter().cloned().sum1::<i32>(), Some(55));
+}
+
+#[test]
+fn product1() {
+    let v: &[i32] = &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
+    assert_eq!(v[..0].iter().cloned().product1::<i32>(), None);
+    assert_eq!(v[..1].iter().cloned().product1::<i32>(), Some(0));
+    assert_eq!(v[1..3].iter().cloned().product1::<i32>(), Some(2));
+    assert_eq!(v[1..5].iter().cloned().product1::<i32>(), Some(24));
+}