Repository: vks/average (language: rust)

Issue:
Implement FromParallelIterator
Hi authors!! Thank you very much for your great work!

I would like to ask for a new feature for greater interoperability: to implement the traits
- FromParallelIterator<f64>
- FromParallelIterator<&'a f64>

For now, I am doing the following:
```rust
let result: Variance = parallel_iterator.collect::<Vec<f64>>().iter().collect();
``` 

Cheers!


Discussion / hints:
About which `FromParallelIterator` trait are you talking? The one from the `rayon` crate?

Fix patch:
diff --git a/.travis.yml b/.travis.yml
index 179bf73..c4ad7ec 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -7,6 +7,8 @@ env:
   - ARCH=i686 FEATURES=''
   - ARCH=x86_64 FEATURES='serde1'
   - ARCH=i686 FEATURES='serde1'
+  - ARCH=x86_64 FEATURES='rayon'
+  - ARCH=i686 FEATURES='rayon'
 
 matrix:
   include:
@@ -16,7 +18,7 @@ matrix:
       script:
         - cargo generate-lockfile -Z minimal-versions
         # Building the test results in problems with rand 0.6
-        - cargo check --features serde1
+        - cargo check --features serde1,rayon
 
 addons:
   apt:
diff --git a/Cargo.toml b/Cargo.toml
index 2fa24f9..ad70de4 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -32,6 +32,7 @@ float-ord = "0.2"
 serde = { version = "1", features = ["derive"], optional = true }
 serde_derive = { version = "1", optional = true }
 serde-big-array = { version = "0.2.0", optional = true }
+rayon = { version = "1.3", optional = true }
 
 [dependencies.conv]
 default-features = false
diff --git a/README.md b/README.md
index b818f8e..68f8561 100644
--- a/README.md
+++ b/README.md
@@ -30,9 +30,10 @@ This crate works without `std`.
 
 ## Crate features
 
-The following optional feature is available:
+The following optional features are available:
 
 * `serde1` enables serialization, via Serde version 1.
+* `rayon` enables support for `rayon::iter::FromParallelIterator`.
 
 
 ## Rust version requirements
diff --git a/src/macros.rs b/src/macros.rs
index e18630e..45331d5 100644
--- a/src/macros.rs
+++ b/src/macros.rs
@@ -131,6 +131,20 @@ macro_rules! concatenate {
         }
 
         $crate::impl_from_iterator!($name);
+
+        // This should be conditionally activated if all fields implement `Merge`.
+        // Could probably be implemented with specialization.
+        /*
+        impl $crate::Merge for $name {
+            #[inline]
+            fn merge(&mut self, other: &Self) {
+                use $crate::Merge;
+                $(
+                    self.$field.merge(&other.$field);
+                )*
+            }
+        }
+        */
     };
 }
 
@@ -161,5 +175,53 @@ macro_rules! impl_from_iterator {
                 e
             }
         }
+    }
+}
+
+/// Implement `FromParallelIterator<f64>` for an iterative estimator.
+///
+/// This will do nothing unless the `rayon` feature is enabled.
+#[macro_export]
+macro_rules! impl_from_par_iterator {
+    ( $name:ident ) => {
+        #[cfg(feature = "rayon")]
+        impl ::rayon::iter::FromParallelIterator<f64> for $name {
+            fn from_par_iter<I>(par_iter: I) -> $name
+                where I: ::rayon::iter::IntoParallelIterator<Item = f64>,
+                      Self: $crate::Merge,
+            {
+                use $crate::Merge;
+                use ::rayon::iter::ParallelIterator;
+
+                let par_iter = par_iter.into_par_iter();
+                par_iter.fold(|| $name::new(), |mut e, i| {
+                    e.add(i);
+                    e
+                }).reduce(|| $name::new(), |mut a, b| {
+                    a.merge(&b);
+                    a
+                })
+            }
+        }
+
+        #[cfg(feature = "rayon")]
+        impl<'a> ::rayon::iter::FromParallelIterator<&'a f64> for $name {
+            fn from_par_iter<I>(par_iter: I) -> $name
+                where I: ::rayon::iter::IntoParallelIterator<Item = &'a f64>,
+                      Self: $crate::Merge,
+            {
+                use $crate::Merge;
+                use ::rayon::iter::ParallelIterator;
+
+                let par_iter = par_iter.into_par_iter();
+                par_iter.fold(|| $name::new(), |mut e, i| {
+                    e.add(*i);
+                    e
+                }).reduce(|| $name::new(), |mut a, b| {
+                    a.merge(&b);
+                    a
+                })
+            }
+        }
     };
 }
diff --git a/src/minmax.rs b/src/minmax.rs
index 4125861..b8888cf 100644
--- a/src/minmax.rs
+++ b/src/minmax.rs
@@ -58,6 +58,7 @@ impl core::default::Default for Min {
 }
 
 impl_from_iterator!(Min);
+impl_from_par_iterator!(Min);
 
 impl Estimate for Min {
     #[inline]
@@ -138,6 +139,7 @@ impl core::default::Default for Max {
 }
 
 impl_from_iterator!(Max);
+impl_from_par_iterator!(Max);
 
 impl Estimate for Max {
     #[inline]
diff --git a/src/moments/kurtosis.rs b/src/moments/kurtosis.rs
index 5799507..e98f4c9 100644
--- a/src/moments/kurtosis.rs
+++ b/src/moments/kurtosis.rs
@@ -148,3 +148,4 @@ impl Merge for Kurtosis {
 }
 
 impl_from_iterator!(Kurtosis);
+impl_from_par_iterator!(Kurtosis);
diff --git a/src/moments/mean.rs b/src/moments/mean.rs
index baf0461..5368c22 100644
--- a/src/moments/mean.rs
+++ b/src/moments/mean.rs
@@ -125,3 +125,4 @@ impl Merge for Mean {
 }
 
 impl_from_iterator!(Mean);
+impl_from_par_iterator!(Mean);
diff --git a/src/moments/mod.rs b/src/moments/mod.rs
index 1ebd857..30f2dfa 100644
--- a/src/moments/mod.rs
+++ b/src/moments/mod.rs
@@ -240,6 +240,7 @@ macro_rules! define_moments_common {
         }
 
         $crate::impl_from_iterator!($name);
+        $crate::impl_from_par_iterator!($name);
     );
 }
 
diff --git a/src/moments/skewness.rs b/src/moments/skewness.rs
index e2a8cac..c1ed8a6 100644
--- a/src/moments/skewness.rs
+++ b/src/moments/skewness.rs
@@ -138,3 +138,4 @@ impl Merge for Skewness {
 }
 
 impl_from_iterator!(Skewness);
+impl_from_par_iterator!(Skewness);
diff --git a/src/moments/variance.rs b/src/moments/variance.rs
index 0520611..08c187d 100644
--- a/src/moments/variance.rs
+++ b/src/moments/variance.rs
@@ -161,3 +161,4 @@ impl Merge for Variance {
 }
 
 impl_from_iterator!(Variance);
+impl_from_par_iterator!(Variance);


Test patch:
diff --git a/tests/mean.rs b/tests/mean.rs
index 8f50c41..2457868 100644
--- a/tests/mean.rs
+++ b/tests/mean.rs
@@ -44,6 +44,18 @@ fn simple_serde() {
     assert_almost_eq!(c.error(), f64::sqrt(0.5), 1e-16);
 }
 
+#[cfg(feature = "rayon")]
+#[test]
+fn simple_rayon() {
+    use rayon::iter::{IntoParallelIterator, ParallelIterator};
+
+    let a: MeanWithError = (1..6).into_par_iter().map(f64::from).collect();
+    assert_eq!(a.mean(), 3.0);
+    assert_eq!(a.len(), 5);
+    assert_eq!(a.sample_variance(), 2.5);
+    assert_almost_eq!(a.error(), f64::sqrt(0.5), 1e-16);
+}
+
 #[test]
 fn numerically_unstable() {
     // The naive algorithm fails for this example due to cancelation.