use crate::error::Error;
use std::fmt;
use std::str::FromStr;
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Model {
Constant,
Logarithmic,
Linear,
Linearithmic,
Quadratic,
Cubic,
Polynomial,
Exponential,
}
pub(crate) const ALL: [Model; 8] = [
Model::Constant,
Model::Logarithmic,
Model::Linear,
Model::Linearithmic,
Model::Quadratic,
Model::Cubic,
Model::Polynomial,
Model::Exponential,
];
const LOG_DEGREE: f64 = 0.13;
impl Model {
pub fn notation(self) -> &'static str {
match self {
Model::Constant => "O(1)",
Model::Logarithmic => "O(log n)",
Model::Linear => "O(n)",
Model::Linearithmic => "O(n log n)",
Model::Quadratic => "O(n^2)",
Model::Cubic => "O(n^3)",
Model::Polynomial => "O(n^m)",
Model::Exponential => "O(c^n)",
}
}
pub(crate) fn has_free_exponent(&self) -> bool {
matches!(self, Model::Polynomial)
}
pub(crate) fn upper_degree(&self) -> f64 {
match self {
Model::Polynomial => f64::MAX,
other => other.nominal_degree(),
}
}
pub(crate) fn lower_degree(&self) -> f64 {
match self {
Model::Polynomial => 0.0,
other => other.nominal_degree(),
}
}
fn nominal_degree(&self) -> f64 {
match self {
Model::Constant => 0.0,
Model::Logarithmic => LOG_DEGREE,
Model::Linear => 1.0,
Model::Linearithmic => 1.0 + LOG_DEGREE,
Model::Quadratic => 2.0,
Model::Cubic => 3.0,
Model::Polynomial => 1.0,
Model::Exponential => f64::INFINITY,
}
}
}
impl FromStr for Model {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match &s.to_lowercase()[..] {
"o(1)" | "constant" => Ok(Model::Constant),
"o(log n)" | "logarithmic" => Ok(Model::Logarithmic),
"o(n)" | "linear" => Ok(Model::Linear),
"o(n log n)" | "linearithmic" => Ok(Model::Linearithmic),
"o(n^2)" | "quadratic" => Ok(Model::Quadratic),
"o(n^3)" | "cubic" => Ok(Model::Cubic),
"o(n^m)" | "polynomial" => Ok(Model::Polynomial),
"o(c^n)" | "exponential" => Ok(Model::Exponential),
_ => Err(Error::ParseNotation),
}
}
}
impl fmt::Display for Model {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.notation())
}
}
#[cfg(test)]
mod tests {
use super::*;
const NOTATION_TEST_CASES: [(&str, Model); 8] = [
("O(1)", Model::Constant),
("O(log n)", Model::Logarithmic),
("O(n)", Model::Linear),
("O(n log n)", Model::Linearithmic),
("O(n^2)", Model::Quadratic),
("O(n^3)", Model::Cubic),
("O(n^m)", Model::Polynomial),
("O(c^n)", Model::Exponential),
];
const NAMED_TEST_CASES: [(&str, Model); 8] = [
("Constant", Model::Constant),
("Logarithmic", Model::Logarithmic),
("Linear", Model::Linear),
("Linearithmic", Model::Linearithmic),
("Quadratic", Model::Quadratic),
("Cubic", Model::Cubic),
("Polynomial", Model::Polynomial),
("Exponential", Model::Exponential),
];
#[test]
fn writes_its_notation() {
for (string, model) in NOTATION_TEST_CASES {
assert_eq!(model.notation(), string);
assert_eq!(model.to_string(), string, "Display must agree");
}
}
#[test]
fn parses_notation_and_name() {
for (string, model) in [NOTATION_TEST_CASES, NAMED_TEST_CASES].concat() {
assert_eq!(string.parse::<Model>(), Ok(model));
}
}
#[test]
fn parses_back_what_it_writes() {
for model in ALL {
assert_eq!(model.notation().parse::<Model>(), Ok(model));
}
}
#[test]
fn rejects_unknown_notation() {
assert_eq!(
"irrelevant text".parse::<Model>(),
Err(Error::ParseNotation)
);
}
#[test]
fn named_models_are_ordered_by_growth() {
let degrees: Vec<f64> = ALL
.iter()
.filter(|m| !m.has_free_exponent())
.map(|m| m.nominal_degree())
.collect();
assert!(
degrees.windows(2).all(|pair| pair[0] < pair[1]),
"ALL should list models in ascending order of growth, got {degrees:?}"
);
}
#[test]
fn polynomial_spans_every_finite_degree() {
assert!(Model::Cubic.upper_degree() < Model::Polynomial.upper_degree());
assert!(Model::Polynomial.upper_degree() < Model::Exponential.upper_degree());
assert_eq!(
Model::Polynomial.lower_degree(),
Model::Constant.lower_degree()
);
}
}