Implement expression normalisation
This commit is contained in:
1 parent
523eec93e4
commit
f58d37b27b
3 files changed
+319
No files matched your search
@@ -1,3 +1,9 @@
|
||||
pub mod normalise;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
}
|
||||
|
||||
pub trait Evaluator {
|
||||
type Expr;
|
||||
type Error;
|
||||
|
||||
@@ -0,0 +1,305 @@
|
||||
use crate::expression::Expression;
|
||||
use crate::eval::{Error, Evaluator};
|
||||
|
||||
pub struct Normaliser;
|
||||
|
||||
impl Normaliser {
|
||||
pub fn new() -> Self {
|
||||
Normaliser {}
|
||||
}
|
||||
}
|
||||
|
||||
impl Evaluator for Normaliser {
|
||||
type Expr = Expression;
|
||||
type Error = Error;
|
||||
|
||||
fn eval(&mut self, input: Expression) -> Result<Expression, Error> {
|
||||
Ok(normalise(input))
|
||||
}
|
||||
}
|
||||
|
||||
fn normalise(expr: Expression) -> Expression {
|
||||
match expr {
|
||||
Expression::Integer(_) => expr,
|
||||
Expression::Symbol(_) => expr,
|
||||
|
||||
Expression::Sum(x, y) =>
|
||||
Expression::Sum(normalise_boxed(x), normalise_boxed(y)),
|
||||
Expression::Product(x, y) =>
|
||||
Expression::Product(normalise_boxed(x), normalise_boxed(y)),
|
||||
Expression::Exponent(x, y) =>
|
||||
Expression::Exponent(normalise_boxed(x), normalise_boxed(y)),
|
||||
|
||||
Expression::Difference(x, y) =>
|
||||
Expression::Sum(
|
||||
normalise_boxed(x), negation(normalise_boxed(y))),
|
||||
Expression::Quotient(x, y) => {
|
||||
if x.integer().is_some() && y.integer().is_some() {
|
||||
Expression::Quotient(x, y)
|
||||
} else {
|
||||
Expression::Product(
|
||||
normalise_boxed(x), reciprocal(normalise_boxed(y)))
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn normalise_boxed(
|
||||
mut b: std::boxed::Box<Expression>) -> std::boxed::Box<Expression> {
|
||||
*b = normalise(*b);
|
||||
b
|
||||
}
|
||||
|
||||
fn negation(
|
||||
expr: std::boxed::Box<Expression>) -> std::boxed::Box<Expression> {
|
||||
let res = Expression::Product(
|
||||
std::boxed::Box::new(Expression::Integer(-1)), expr);
|
||||
std::boxed::Box::new(res)
|
||||
}
|
||||
|
||||
fn reciprocal(
|
||||
expr: std::boxed::Box<Expression>) -> std::boxed::Box<Expression> {
|
||||
let res = if expr.is_constant() {
|
||||
Expression::Quotient(
|
||||
std::boxed::Box::new(Expression::Integer(1)), expr)
|
||||
} else {
|
||||
Expression::Exponent(
|
||||
expr, std::boxed::Box::new(Expression::Integer(-1)))
|
||||
};
|
||||
std::boxed::Box::new(res)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn integer_is_unchanged() {
|
||||
let input = Expression::Integer(0);
|
||||
let result = Normaliser::new().eval(input).unwrap();
|
||||
assert_eq!(result, Expression::Integer(0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn symbol_is_unchanged() {
|
||||
let input = Expression::Symbol('x');
|
||||
let result = Normaliser::new().eval(input).unwrap();
|
||||
assert_eq!(result, Expression::Symbol('x'));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sum_of_integers_is_unchanged() {
|
||||
let input = Expression::sum(
|
||||
Expression::Symbol('x'),
|
||||
Expression::Symbol('x'));
|
||||
let result = Normaliser::new().eval(input).unwrap();
|
||||
assert_eq!(
|
||||
result,
|
||||
Expression::sum(
|
||||
Expression::Symbol('x'),
|
||||
Expression::Symbol('x')));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn product_of_integers_is_unchanged() {
|
||||
let input = Expression::product(
|
||||
Expression::Symbol('x'),
|
||||
Expression::Symbol('x'));
|
||||
let result = Normaliser::new().eval(input).unwrap();
|
||||
assert_eq!(
|
||||
result,
|
||||
Expression::product(
|
||||
Expression::Symbol('x'),
|
||||
Expression::Symbol('x')));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exponent_of_integers_is_unchanged() {
|
||||
let input = Expression::exponent(
|
||||
Expression::Symbol('x'),
|
||||
Expression::Symbol('x'));
|
||||
let result = Normaliser::new().eval(input).unwrap();
|
||||
assert_eq!(
|
||||
result,
|
||||
Expression::exponent(
|
||||
Expression::Symbol('x'),
|
||||
Expression::Symbol('x')));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn difference_is_transformed_to_sum() {
|
||||
let input = Expression::difference(
|
||||
Expression::Symbol('x'),
|
||||
Expression::Symbol('x'));
|
||||
let result = Normaliser::new().eval(input).unwrap();
|
||||
assert_eq!(
|
||||
result,
|
||||
Expression::sum(
|
||||
Expression::Symbol('x'),
|
||||
Expression::product(
|
||||
Expression::Integer(-1),
|
||||
Expression::Symbol('x'))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn quotient_of_symbols_is_transformed_to_product() {
|
||||
let input = Expression::quotient(
|
||||
Expression::Symbol('x'),
|
||||
Expression::Symbol('x'));
|
||||
let result = Normaliser::new().eval(input).unwrap();
|
||||
assert_eq!(
|
||||
result,
|
||||
Expression::product(
|
||||
Expression::Symbol('x'),
|
||||
Expression::exponent(
|
||||
Expression::Symbol('x'),
|
||||
Expression::Integer(-1))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn quotient_of_integer_and_symbol_is_transformed_to_product() {
|
||||
let input = Expression::quotient(
|
||||
Expression::Integer(2),
|
||||
Expression::Symbol('x'));
|
||||
let result = Normaliser::new().eval(input).unwrap();
|
||||
assert_eq!(
|
||||
result,
|
||||
Expression::product(
|
||||
Expression::Integer(2),
|
||||
Expression::exponent(
|
||||
Expression::Symbol('x'),
|
||||
Expression::Integer(-1))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn quotient_of_symbol_and_integer_is_transformed_to_product() {
|
||||
let input = Expression::quotient(
|
||||
Expression::Symbol('x'),
|
||||
Expression::Integer(2));
|
||||
let result = Normaliser::new().eval(input).unwrap();
|
||||
assert_eq!(
|
||||
result,
|
||||
Expression::product(
|
||||
Expression::Symbol('x'),
|
||||
Expression::quotient(
|
||||
Expression::Integer(1),
|
||||
Expression::Integer(2))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn differences_inside_sum_are_normalised() {
|
||||
let input = Expression::sum(
|
||||
Expression::difference(
|
||||
Expression::Symbol('x'),
|
||||
Expression::Symbol('x')),
|
||||
Expression::difference(
|
||||
Expression::Symbol('x'),
|
||||
Expression::Symbol('x')));
|
||||
let result = Normaliser::new().eval(input).unwrap();
|
||||
assert_eq!(
|
||||
result,
|
||||
Expression::sum(
|
||||
Expression::sum(
|
||||
Expression::Symbol('x'),
|
||||
Expression::product(
|
||||
Expression::Integer(-1),
|
||||
Expression::Symbol('x'))),
|
||||
Expression::sum(
|
||||
Expression::Symbol('x'),
|
||||
Expression::product(
|
||||
Expression::Integer(-1),
|
||||
Expression::Symbol('x')))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn differences_inside_exponent_are_normalised() {
|
||||
let input = Expression::exponent(
|
||||
Expression::difference(
|
||||
Expression::Symbol('x'),
|
||||
Expression::Symbol('x')),
|
||||
Expression::difference(
|
||||
Expression::Symbol('x'),
|
||||
Expression::Symbol('x')));
|
||||
let result = Normaliser::new().eval(input).unwrap();
|
||||
assert_eq!(
|
||||
result,
|
||||
Expression::exponent(
|
||||
Expression::sum(
|
||||
Expression::Symbol('x'),
|
||||
Expression::product(
|
||||
Expression::Integer(-1),
|
||||
Expression::Symbol('x'))),
|
||||
Expression::sum(
|
||||
Expression::Symbol('x'),
|
||||
Expression::product(
|
||||
Expression::Integer(-1),
|
||||
Expression::Symbol('x')))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn differences_inside_difference_are_normalised() {
|
||||
let input = Expression::difference(
|
||||
Expression::difference(
|
||||
Expression::Symbol('x'),
|
||||
Expression::Symbol('x')),
|
||||
Expression::difference(
|
||||
Expression::Symbol('x'),
|
||||
Expression::Symbol('x')));
|
||||
let result = Normaliser::new().eval(input).unwrap();
|
||||
assert_eq!(
|
||||
result,
|
||||
Expression::sum(
|
||||
Expression::sum(
|
||||
Expression::Symbol('x'),
|
||||
Expression::product(
|
||||
Expression::Integer(-1),
|
||||
Expression::Symbol('x'))),
|
||||
Expression::product(
|
||||
Expression::Integer(-1),
|
||||
Expression::sum(
|
||||
Expression::Symbol('x'),
|
||||
Expression::product(
|
||||
Expression::Integer(-1),
|
||||
Expression::Symbol('x'))))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn differences_inside_quotient_are_normalised() {
|
||||
let input = Expression::quotient(
|
||||
Expression::difference(
|
||||
Expression::Symbol('x'),
|
||||
Expression::Symbol('x')),
|
||||
Expression::difference(
|
||||
Expression::Symbol('x'),
|
||||
Expression::Symbol('x')));
|
||||
let result = Normaliser::new().eval(input).unwrap();
|
||||
assert_eq!(
|
||||
result,
|
||||
Expression::product(
|
||||
Expression::sum(
|
||||
Expression::Symbol('x'),
|
||||
Expression::product(
|
||||
Expression::Integer(-1),
|
||||
Expression::Symbol('x'))),
|
||||
Expression::exponent(
|
||||
Expression::sum(
|
||||
Expression::Symbol('x'),
|
||||
Expression::product(
|
||||
Expression::Integer(-1),
|
||||
Expression::Symbol('x'))),
|
||||
Expression::Integer(-1))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn quotient_of_integers_is_unchanged() {
|
||||
let input = Expression::quotient(
|
||||
Expression::Integer(1),
|
||||
Expression::Integer(2));
|
||||
let result = Normaliser::new().eval(input).unwrap();
|
||||
assert_eq!(
|
||||
result,
|
||||
Expression::quotient(
|
||||
Expression::Integer(1),
|
||||
Expression::Integer(2)));
|
||||
}
|
||||
}
|
||||
@@ -35,6 +35,14 @@ impl Expression {
|
||||
std::boxed::Box::new(left), std::boxed::Box::new(right))
|
||||
}
|
||||
|
||||
pub fn integer(&self) -> Option<i32> {
|
||||
if let Expression::Integer(x) = self {
|
||||
Some(*x)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_constant(&self) -> bool {
|
||||
match self {
|
||||
Expression::Integer(_) => true,
|
||||
|
||||
Reference in new issue
Block a user