type F = fn(f64) -> f64;
#[derive(Clone)]
pub enum Calculation {
Rate(f64),
Func { forward: F, reverse: F },
}
pub struct Conversion {
pub source_unit: String,
pub calculation: Calculation,
pub target_unit: String,
}
impl PartialEq for Conversion {
fn eq(&self, other: &Conversion) -> bool {
self.source_unit == other.source_unit && self.target_unit == other.target_unit
}
}
impl Conversion {
pub fn new(source_unit: &str, calculation: Calculation, target_unit: &str) -> Conversion {
Conversion {
source_unit: String::from(source_unit),
calculation,
target_unit: String::from(target_unit),
}
}
pub fn invert(&self) -> Conversion {
Conversion {
source_unit: self.target_unit.clone(),
calculation: match &self.calculation {
Calculation::Rate(r) => Calculation::Rate(1.0 / r),
Calculation::Func { forward, reverse } => Calculation::Func {
forward: *reverse,
reverse: *forward,
},
},
target_unit: self.source_unit.clone(),
}
}
}
pub fn find_conversion_path<'a>(
source_unit: &'a str,
target_unit: &'a str,
conversions: &'a Vec<Conversion>,
) -> Option<Vec<&'a Conversion>> {
let paths = find_paths(source_unit, target_unit, conversions, Vec::new());
let mut weighted_paths: Vec<_> = paths.iter().map(|p| (p, p.len())).collect();
weighted_paths.sort_by(|(_, m), (_, n)| m.cmp(n));
weighted_paths.into_iter().map(|(p, _)| p).next().cloned()
}
fn find_paths<'a>(
unit: &'a str,
target_unit: &'a str,
conversions: &'a Vec<Conversion>,
path: Vec<&'a Conversion>,
) -> Vec<Vec<&'a Conversion>> {
let candidates: Vec<&'a Conversion> = conversions
.iter()
.filter(|c| c.source_unit == unit && !path.contains(c))
.collect();
let mut paths: Vec<Vec<&Conversion>> = Vec::new();
for candidate in candidates {
let mut new_path = path.clone();
new_path.push(candidate);
if candidate.target_unit == target_unit {
paths.push(new_path);
} else {
for found in find_paths(&candidate.target_unit, target_unit, conversions, new_path) {
paths.push(found);
}
}
}
paths
}