use std::fmt;
#[macro_use]
extern crate lazy_static;
#[derive(PartialEq, Eq, Debug)]
pub struct FriendlyProbability {
numerator: u8,
denominator: u8,
friendly_description: &'static str,
friendly_string: String
}
impl fmt::Display for FriendlyProbability {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.friendly_string)
}
}
impl FriendlyProbability {
pub fn new<T: Into<Option<String>>>(numerator: u8, denominator: u8, friendly_description: &'static str, friendly_string: T) -> FriendlyProbability {
let real_friendly_string = friendly_string.into().unwrap_or_else(|| format!("{} in {}", numerator, denominator));
FriendlyProbability {
numerator,
denominator,
friendly_description,
friendly_string: real_friendly_string
}
}
pub fn numerator(self: &FriendlyProbability) -> u8 {
self.numerator
}
pub fn denominator(self: &FriendlyProbability) -> u8 {
self.denominator
}
pub fn friendly_description(self: &FriendlyProbability) -> &str {
&self.friendly_description
}
pub fn friendly_string(self: &FriendlyProbability) -> &str {
&self.friendly_string
}
pub fn from_probability(probability: f32) -> FriendlyProbability {
if probability < 0.0 || probability > 1.0 {
panic!("probability is less than 0 or greater than 1!")
}
let friendly_description_location = FRIENDLY_DESCRIPTIONS.binary_search_by(|f| {
f.0.partial_cmp(&probability).expect("Couldn't compare floats?")
});
let friendly_description = match friendly_description_location {
Ok(i) => FRIENDLY_DESCRIPTIONS[i].1,
Err(i) => FRIENDLY_DESCRIPTIONS[i - 1].1
};
if probability == 0.0 {
return FriendlyProbability::new(0, 1, friendly_description, None)
}
if probability == 1.0 {
return FriendlyProbability::new(1, 1, friendly_description, None)
}
if probability > 0.99 {
return FriendlyProbability::new(99, 100, friendly_description, String::from(">99 in 100"))
}
if probability < 0.01 {
return FriendlyProbability::new(1, 100, friendly_description, String::from("<1 in 100"))
}
let data = &FRACTION_DATA;
let fraction_to_compare = Fraction::new_for_comparison(probability);
let location = data.binary_search_by(|f| {
f.partial_cmp(&fraction_to_compare).expect("Couldn't compare values?")
});
fn friendly_probability_from_fraction(fraction: &Fraction, friendly_description: &'static str) -> FriendlyProbability {
FriendlyProbability::new(fraction.numerator, fraction.denominator, friendly_description, None)
}
let data_len = data.len();
match location {
Ok(i) => friendly_probability_from_fraction(&data[i], friendly_description),
Err(i) => {
if i == 0 {
return friendly_probability_from_fraction(&data[0], friendly_description);
}
if i == data_len {
return friendly_probability_from_fraction(&data[data_len - 1], friendly_description);
}
if probability - (&data[i - 1]).value < (&data[i]).value - probability {
return friendly_probability_from_fraction(&data[i - 1], friendly_description);
}
else {
return friendly_probability_from_fraction(&data[i], friendly_description);
}
}
}
}
}
#[derive(PartialOrd, PartialEq, Debug)]
struct Fraction {
value: f32,
numerator: u8,
denominator: u8,
}
impl Fraction {
pub fn new(numerator: u8, denominator: u8) -> Fraction {
Fraction {
numerator: numerator,
denominator: denominator,
value: numerator as f32/denominator as f32
}
}
fn new_for_comparison(value: f32) -> Fraction {
Fraction {
numerator: 0,
denominator: 0,
value
}
}
}
const FRIENDLY_DESCRIPTIONS : [(f32, &str); 14] = [
(0.0, "Hard to imagine"),
(0.005, "Barely possible"),
(0.02, "Still possible"),
(0.08, "Some chance"),
(0.15, "Could happen"),
(0.2, "Perhaps"),
(0.45, "Flip a coin"),
(0.55, "Likelier than not"),
(0.7, "Good chance"),
(0.8, "Probably"),
(0.85, "Quite likely"),
(0.9, "Pretty likely"),
(0.95, "Very likely"),
(0.995, "Almost certainly"),
];
lazy_static! {
static ref FRACTION_DATA: Vec<Fraction> = {
let mut fractions : Vec<Fraction> = Vec::new();
fn gcd(x: u8, y: u8) -> u8 {
let mut x = x;
let mut y = y;
while y != 0 {
let t = y;
y = x % y;
x = t;
}
x
}
for d in 2..11 {
for n in 1..d {
if gcd(n, d) == 1 {
fractions.push(Fraction::new(n, d));
}
}
}
for &d in [12, 15, 20, 30, 40, 50, 60, 80, 100].iter() {
fractions.push(Fraction::new(1, d));
fractions.push(Fraction::new(d - 1, d));
}
fractions.sort_unstable_by(|a,b| a.partial_cmp(b).unwrap());
fractions
};
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn friendly_probability_string_matches_numeric_inputs() {
let fp = FriendlyProbability::new(1, 2, "", None);
assert_eq!("1 in 2", fp.friendly_string);
}
#[test]
fn friendly_probability_string_matches_string_input() {
let s = String::from("something weird");
let fp = FriendlyProbability::new(1, 2, "", s.clone());
assert_eq!(s, fp.friendly_string);
}
#[test]
#[should_panic]
fn friendly_probability_from_fraction_less_than_0() {
FriendlyProbability::from_probability(-0.01);
}
#[test]
#[should_panic]
fn friendly_probability_from_fraction_greater_than_1() {
FriendlyProbability::from_probability(1.01);
}
}