Macro for concatenating iterative estimators

This commit is contained in:
Vinzent Steinberg 2017-06-21 16:06:37 +02:00
parent 0eed9d06db
commit 01157da831
2 changed files with 124 additions and 50 deletions

View File

@ -36,6 +36,20 @@
//! * Quantiles ([`Quantile`]).
//! * Minimum ([`Min`]) and maximum ([`Max`]).
//!
//! ## Estimating several statistics at once
//!
//! The estimators are designed to have minimal state. The recommended way to
//! calculate several of them at once is to create a struct with all the
//! estimators you need. You can then implement `add` for your struct by
//! forwarding to the underlying estimators. Everything is inlined, so there
//! should be no overhead.
//!
//! You can avoid the boilerplate code by using the [`concatenate`] macro.
//!
//! Note that calculating moments requires calculating the lower moments, so you
//! only need to include the highest moment in your struct.
//!
//!
//! [`Mean`]: ./struct.Mean.html
//! [`MeanWithError`]: ./type.MeanWithError.html
//! [`WeightedMean`]: ./struct.WeightedMean.html
@ -46,56 +60,7 @@
//! [`Quantile`]: ./struct.Quantile.html
//! [`Min`]: ./struct.Min.html
//! [`Max`]: ./struct.Max.html
//!
//!
//! ## Estimating several statistics at once
//!
//! The estimators are designed to have minimal state. The recommended way to
//! calculate several of them at once is to create a struct with all the
//! estimators you need. You can then implement `add` for your struct by
//! forwarding to the underlying estimators.
//!
//! Note that calculating moments requires calculating the lower moments, so you
//! only need to include the highest moment in your struct.
//!
//!
//! ### Example
//!
//! ```
//! use average::{Min, Max};
//!
//! struct MinMax {
//! min: Min,
//! max: Max,
//! }
//!
//! impl MinMax {
//! pub fn new() -> MinMax {
//! MinMax { min: Min::new(), max: Max::new() }
//! }
//!
//! pub fn add(&mut self, x: f64) {
//! self.min.add(x);
//! self.max.add(x);
//! }
//!
//! pub fn min(&self) -> f64 {
//! self.min.min()
//! }
//!
//! pub fn max(&self) -> f64 {
//! self.max.max()
//! }
//! }
//!
//! let mut s = MinMax::new();
//! for i in 1..6 {
//! s.add(i as f64);
//! }
//!
//! assert_eq!(s.min(), 1.0);
//! assert_eq!(s.max(), 5.0);
//! ```
//! [`concatenate`]: ./macro.concatenate.html
#![cfg_attr(feature = "cargo-clippy", allow(float_cmp, map_clone))]

View File

@ -14,3 +14,112 @@ macro_rules! assert_almost_eq {
}
);
}
/// Concatenate several iterative estimators into one.
///
/// `$name` is the name of the new type. `$statistic` is the name of a statistic
/// and must exist as a method of the corresponding type `$estimator`.
/// `$estimator` must have an `add` method for adding new observations to the
/// sample (taking an `f64` as an argument). It must also implement `Default`.
///
/// For moments, only an estimator for the highest moment should be used and
/// reused for the lower moments. This is currently not supported by this macro
/// and has to be done by hand.
///
///
/// # Example
///
/// ```
/// # extern crate core;
/// # #[macro_use] extern crate average;
/// # fn main() {
/// use average::{Min, Max};
///
/// concatenate!(MinMax, min, Min, max, Max);
///
/// let s: MinMax = (1..6).map(Into::into).collect();
///
/// assert_eq!(s.min(), 1.0);
/// assert_eq!(s.max(), 5.0);
/// # }
/// ```
///
/// The generated code looks roughly like this:
///
/// ```
/// # use average::{Min, Max};
/// #
/// struct MinMax {
/// min: Min,
/// max: Max,
/// }
///
/// impl MinMax {
/// pub fn new() -> MinMax {
/// MinMax { min: Min::default(), max: Max::default() }
/// }
///
/// pub fn add(&mut self, x: f64) {
/// self.min.add(x);
/// self.max.add(x);
/// }
///
/// pub fn min(&self) -> f64 {
/// self.min.min()
/// }
///
/// pub fn max(&self) -> f64 {
/// self.max.max()
/// }
/// }
/// ```
#[macro_export]
macro_rules! concatenate {
( $name:ident, $($statistic:ident, $estimator:ident),* ) => {
struct $name {
$(
$statistic: $estimator,
)*
}
impl $name {
pub fn new() -> $name {
$name {
$(
$statistic: ::core::default::Default::default(),
)*
}
}
pub fn add(&mut self, x: f64) {
$(
self.$statistic.add(x);
)*
}
$(
pub fn $statistic(&self) -> f64 {
self.$statistic.$statistic()
}
)*
}
impl Default for $name {
fn default() -> $name {
$name::new()
}
}
impl ::core::iter::FromIterator<f64> for $name {
fn from_iter<T>(iter: T) -> $name
where T: IntoIterator<Item=f64>
{
let mut e = $name::new();
for i in iter {
e.add(i);
}
e
}
}
};
}