pub mod polynomial;
pub use polynomial::Polynomial;
use core::fmt;
use num_traits::Num;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Op {
Add,
Sub,
Mul,
Div,
Rem,
}
impl Op {
pub fn symbol(self) -> char {
match self {
Op::Add => '+',
Op::Sub => '-',
Op::Mul => '*',
Op::Div => '/',
Op::Rem => '%',
}
}
pub fn from_symbol(c: char) -> Option<Self> {
Some(match c {
'+' => Op::Add,
'-' => Op::Sub,
'*' => Op::Mul,
'/' => Op::Div,
'%' => Op::Rem,
_ => return None,
})
}
}
impl fmt::Display for Op {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.symbol())
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Equation<T> {
a: T,
b: T,
op: Op,
sol: Option<T>,
}
impl<T: Num + Copy> Equation<T> {
pub fn new(a: T, b: T, op: Op) -> Self {
Self { a, b, op, sol: None }
}
pub fn a(&self) -> T {
self.a
}
pub fn b(&self) -> T {
self.b
}
pub fn op(&self) -> Op {
self.op
}
pub fn set_a(&mut self, a: T) {
self.a = a;
self.sol = None;
}
pub fn set_b(&mut self, b: T) {
self.b = b;
self.sol = None;
}
pub fn set_op(&mut self, op: Op) {
self.op = op;
self.sol = None;
}
pub fn solve(&mut self) -> T {
if let Some(s) = self.sol {
return s;
}
let s = match self.op {
Op::Add => self.a + self.b,
Op::Sub => self.a - self.b,
Op::Mul => self.a * self.b,
Op::Div => self.a / self.b,
Op::Rem => self.a % self.b,
};
self.sol = Some(s);
s
}
pub fn cached(&self) -> Option<T> {
self.sol
}
}
impl<T> fmt::Display for Equation<T>
where
T: fmt::Display + Num + Copy,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} {} {}", self.a, self.op, self.b)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn solves_and_caches() {
let mut e = Equation::new(2i64, 3, Op::Add);
assert_eq!(e.cached(), None);
assert_eq!(e.solve(), 5);
assert_eq!(e.cached(), Some(5));
}
#[test]
fn zero_is_a_real_answer() {
let mut e = Equation::new(3i64, 3, Op::Sub);
assert_eq!(e.solve(), 0);
assert_eq!(e.cached(), Some(0));
e.set_b(2);
assert_eq!(e.cached(), None);
assert_eq!(e.solve(), 1);
}
#[test]
fn every_operator() {
assert_eq!(Equation::new(10i64, 3, Op::Add).solve(), 13);
assert_eq!(Equation::new(10i64, 3, Op::Sub).solve(), 7);
assert_eq!(Equation::new(10i64, 3, Op::Mul).solve(), 30);
assert_eq!(Equation::new(10i64, 3, Op::Div).solve(), 3);
assert_eq!(Equation::new(10i64, 3, Op::Rem).solve(), 1);
}
#[test]
fn works_with_floats() {
let mut e = Equation::new(1.5f64, 2.5, Op::Mul);
assert!((e.solve() - 3.75).abs() < 1e-12);
}
#[test]
fn op_symbol_roundtrip() {
for op in [Op::Add, Op::Sub, Op::Mul, Op::Div, Op::Rem] {
assert_eq!(Op::from_symbol(op.symbol()), Some(op));
}
assert_eq!(Op::from_symbol('?'), None);
}
#[test]
fn display_equation() {
let mut e = Equation::new(4i64, 5, Op::Mul);
let sol = e.solve();
assert_eq!(format!("{} = {}", e, sol), "4 * 5 = 20");
}
#[test]
fn every_setter_invalidates_cache() {
let mut e = Equation::new(10i64, 5, Op::Add);
assert_eq!(e.solve(), 15);
e.set_a(7);
assert_eq!(e.cached(), None);
assert_eq!(e.solve(), 12);
e.set_b(3);
assert_eq!(e.cached(), None);
assert_eq!(e.solve(), 10);
e.set_op(Op::Sub);
assert_eq!(e.cached(), None);
assert_eq!(e.solve(), 4);
}
#[test]
fn solve_is_idempotent() {
let mut e = Equation::new(7i64, 13, Op::Mul);
let first = e.solve();
let second = e.solve();
let third = e.solve();
assert_eq!(first, second);
assert_eq!(second, third);
}
#[test]
fn op_display_matches_symbol() {
for op in [Op::Add, Op::Sub, Op::Mul, Op::Div, Op::Rem] {
assert_eq!(format!("{}", op), op.symbol().to_string());
}
}
#[test]
fn op_enum_value_semantics() {
use std::collections::HashSet;
let set: HashSet<Op> = [Op::Add, Op::Add, Op::Mul, Op::Sub, Op::Sub].into_iter().collect();
assert_eq!(set.len(), 3);
let copy = Op::Mul;
assert_eq!(copy, Op::Mul);
}
#[test]
fn accessor_methods() {
let e = Equation::new(8i64, 2, Op::Div);
assert_eq!(e.a(), 8);
assert_eq!(e.b(), 2);
assert_eq!(e.op(), Op::Div);
assert_eq!(e.cached(), None);
}
}