xdlol 0.0.0

Tests to study random data.
Documentation
//! Randomness tests.
//!
//! # Reference
//!
//! Knuth, D. E. "The Art of Computer Programming, 3.3.1 General Test Procedures for Studying Random Data." _The Art of Computer Programming; Volume 2: Seminumerical Algorithms, Third Edition_: 42-60.
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

mod frequency;
mod chi_square;

use std::hash::Hash;
use std::fmt::Debug;
use num::{Num, ToPrimitive};
use arithmetic::UnsignedArithmetic;
pub use self::frequency::FrequencyMap;
pub use self::chi_square::chi_square;
pub use self::chi_square::chi_square_test;

/// Print a histogram of a collection.
///
/// # Example
///
/// ```
/// use xdlol::statistics::histogram;
///
/// let pi = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 9];
/// histogram(pi.iter(), 42);
/// ```
pub fn histogram<K, I: IntoIterator<Item = K>>(random_numbers: I, width: u8)
where
    K: Hash + Eq + Clone + Copy + Ord + Debug,
{
    let freq_map: FrequencyMap<K, u64> = random_numbers.into_iter().collect();
    let min = freq_map.values().min().unwrap();
    let max = freq_map.values().max().unwrap();
    for (item, count) in freq_map.iter() {
        print!("{:?}: ", item);
        let item_norm = normalize(*count, *min, *max);
        let bar_width: u64 = (item_norm * width as f64) as u64;
        for _ in 0..bar_width as usize {
            print!("*")
        }
        println!("");
    }
}

/// Normalize a value between a minimum and maximum.
///
/// > x_new = (x - x_min) / (x_max - x_min)
///
/// # Panics
///
/// If the inputs can not be converted to `f64`, on divide by zero,
/// or if `value` is outside of the `min` and `max` range.
fn normalize<N>(value: N, min: N, max: N) -> f64
where
    N: Clone + Num + ToPrimitive + UnsignedArithmetic + Debug,
{
    if value < min || value > max {
        panic!("Value out of min/max range");
    }
    let numerator = (value - min.clone())
        .to_f64()
        .expect("Unable to convert `N` to f64");
    let denominator = (max - min).to_f64().expect("Unable to convert `N` to f64");
    let result = numerator / denominator;
    if result.is_nan() {
        panic!("Divide by zero");
    }
    result
}

#[cfg(test)]
mod tests {
    extern crate float_cmp;
    extern crate rand;

    use super::*;
    use self::float_cmp::ApproxEqUlps;

    // -------------------------------------------------------------------------
    // histogram()
    //
    // When visual verification of histogram is desired run
    // `cargo test -- --nocapture` to see the output.
    // -------------------------------------------------------------------------

    #[test]
    fn test_histogram() {
        let test_data = [
            0, 0, 0, 1, 1, 1, 1, 1, 2, 3, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8
        ];
        histogram(test_data.iter(), 42);
    }

    // -------------------------------------------------------------------------
    // normalize()
    // -------------------------------------------------------------------------

    #[test]
    fn test_normalize_simple() {
        assert!(normalize(0, 0, 10).approx_eq_ulps(&0_f64, 2));
        assert!(normalize(1, 0, 10).approx_eq_ulps(&0.1_f64, 2));
        assert!(normalize(2, 0, 10).approx_eq_ulps(&0.2_f64, 2));
        assert!(normalize(3, 0, 10).approx_eq_ulps(&0.3_f64, 2));
        assert!(normalize(4, 0, 10).approx_eq_ulps(&0.4_f64, 2));
        assert!(normalize(5, 0, 10).approx_eq_ulps(&0.5_f64, 2));
        assert!(normalize(6, 0, 10).approx_eq_ulps(&0.6_f64, 2));
        assert!(normalize(7, 0, 10).approx_eq_ulps(&0.7_f64, 2));
        assert!(normalize(8, 0, 10).approx_eq_ulps(&0.8_f64, 2));
        assert!(normalize(9, 0, 10).approx_eq_ulps(&0.9_f64, 2));
        assert!(normalize(10, 0, 10).approx_eq_ulps(&1_f64, 2));
    }

    #[test]
    fn test_normalize_simple_offset() {
        assert!(normalize(10, 10, 20).approx_eq_ulps(&0_f64, 2));
        assert!(normalize(11, 10, 20).approx_eq_ulps(&0.1_f64, 2));
        assert!(normalize(12, 10, 20).approx_eq_ulps(&0.2_f64, 2));
        assert!(normalize(13, 10, 20).approx_eq_ulps(&0.3_f64, 2));
        assert!(normalize(14, 10, 20).approx_eq_ulps(&0.4_f64, 2));
        assert!(normalize(15, 10, 20).approx_eq_ulps(&0.5_f64, 2));
        assert!(normalize(16, 10, 20).approx_eq_ulps(&0.6_f64, 2));
        assert!(normalize(17, 10, 20).approx_eq_ulps(&0.7_f64, 2));
        assert!(normalize(18, 10, 20).approx_eq_ulps(&0.8_f64, 2));
        assert!(normalize(19, 10, 20).approx_eq_ulps(&0.9_f64, 2));
        assert!(normalize(20, 10, 20).approx_eq_ulps(&1_f64, 2));
    }

    #[test]
    #[should_panic]
    fn test_normalize_divide_by_zero() {
        normalize(0, 0, 0);
    }

    #[test]
    #[should_panic]
    fn test_normalize_value_out_of_range_low() {
        normalize(9, 10, 20);
    }

    #[test]
    #[should_panic]
    fn test_normalize_value_out_of_range_high() {
        normalize(21, 10, 20);
    }
}