1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
//! Random number utility functions.

use num::rational::Ratio;
use num::{Bounded, Integer};
use rand;
use rand::distributions::range::SampleRange;
use rand::Rng;
use std::fmt::Display;

/// Trait allowing access to random elements and/or indices in implementing containers.
pub trait Choose<T> {
    /// Returns an element picked randomly from `&self`, or `None` if no elements exist.
    fn choose(&self) -> Option<&T>;

    /// Returns a valid index picked randomly from `&self`, or `None` if no index exists.
    fn choose_index(&self) -> Option<usize>;

    /// Returns a valid (value, index) tuple picked randomly, or `None` if none exist.
    fn choose_enumerate(&self) -> Option<(usize, &T)>;
}

impl<T> Choose<T> for Vec<T> {
    fn choose(&self) -> Option<&T> {
        rand::thread_rng().choose(self)
    }

    fn choose_index(&self) -> Option<usize> {
        if !self.is_empty() {
            Some(rand_int(0, self.len() - 1))
        } else {
            None
        }
    }

    fn choose_enumerate(&self) -> Option<(usize, &T)> {
        let i = self.choose_index();
        match i {
            Some(i) => Some((i, &self[i])),
            None => None,
        }
    }
}

/// Returns a random Integer in the range `[x, y]` inclusive.
pub fn rand_int<T>(x: T, y: T) -> T
where
    T: Integer + SampleRange,
{
    rand::thread_rng().gen_range(x, y + T::one())
}

/// Returns a random Ratio in the inclusive range `[x, y]` with the given denominator.
///
/// # Panics
/// This function can result in an overflow - use only for known inputs.
pub fn rand_ratio<T>(x: T, y: T, d: T) -> Ratio<T>
where
    T: Clone + Copy + Integer + SampleRange,
{
    Ratio::new(rand_int(x * d, y * d), d)
}

/// Returns true with `x` in `y` chance.
pub fn dice<T>(x: T, y: T) -> bool
where
    T: Copy + Display + Integer + SampleRange,
{
    debug_assert!(x <= y, format!("Assert failed: dice({}, {})", x, y));
    rand_int(T::one(), y) <= x
}

/// Returns true with `x` chance, where 0 <= `x` <= 1.
pub fn chance<T>(x: Ratio<T>) -> bool
where
    T: Bounded + Clone + Copy + Integer + SampleRange,
{
    let max = T::max_value() - T::one();
    Ratio::new(rand_int(T::min_value(), max), max) <= x
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::util::math::between;

    #[test]
    fn test_rand_int() {
        for _ in 1..100 {
            let (a, b) = (rand_int(0, 1000), rand_int(0, 1000));
            assert!(between(rand_int(a.min(b), a.max(b)), a, b))
        }
    }

    #[test]
    fn test_dice() {
        for _ in 1..100 {
            assert!(!dice(0, rand_int(1, 100)));
        }
    }
}