I've been trying so hard to come up with a data model that works for mathematical expressions (like x^2 + 2x - y * 4, no equals sign) in Rust. It's very different to other languages I'm most familiar with.
Eventually, I want to be able to
I think of mathematical expressions as recursive structures. As in the BNF
<expr> ::= <sum> | <product> | <reciprocal> | <number> | <variable>
<sum> ::= <expr> "+" <expr>
<product> ::= <expr> "*" <expr>
<reciprocal> ::= "1/" <expr>
<power> ::= <expr> "^" <expr>
<number> ::= ...|-4|-3|-2|-1|0|1|2|3|4|...
<variable> ::= "x" | "y" | "any string literal" | ...
Setting up a recursive data structure in the first place was hard for me. Maybe enum
s are the correct choice, maybe this concept is the wrong approach altogether for Rust.
I've also really struggled a lot with borrowing, dereferencing, ownership, etc.
Please let me know what you think of the code I've written, the approach I've taken and how amenable it will be to my ambitions with this program, and any tips you have for helping me to get into the Rust headspace.
use std::collections::BTreeSet;
use std::fmt;
#[derive(Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Clone)]
enum Expression {
Sum(BTreeSet<(usize, Expression)>),
Product(BTreeSet<(usize, Expression)>),
Negation(Box<Expression>),
Reciprocal(Box<Expression>),
Power(Box<Expression>, Box<Expression>),
Variable(Box<str>),
Number(isize),
}
use Expression::*;
impl fmt::Display for Expression {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
match self {
Sum(summands) => {
let mut flat_summands: Vec<String> = vec![];
for (n, expr) in summands.iter() {
let stringified_expr = expr.to_string();
for _ in 0..*n {
flat_summands.push(format!("{}", stringified_expr));
}
}
fmt.write_str(&flat_summands.into_iter().map(|summand| summand.to_string()).collect::<Vec<_>>().join(" + "))
},
Product(factors) => {
let mut flat_factors: Vec<String> = vec![];
for (n, expr) in factors.iter() {
let stringified_expr = expr.to_string();
for _ in 0..*n {
flat_factors.push(format!("{}", stringified_expr));
}
}
fmt.write_str(&flat_factors.into_iter().map(|factor| factor.to_string()).collect::<Vec<_>>().join(" * "))
},
Reciprocal(ref expr) => fmt.write_fmt(format_args!("1 / {}", expr.to_string())),
Power(ref base, ref exponent) => fmt.write_fmt(format_args!("{} ^ {}", base.to_string(), exponent.to_string())),
Negation(ref expr) => fmt.write_fmt(format_args!("-{}", expr.to_string())),
Variable(ref name) => fmt.write_fmt(format_args!("{}", name)),
Number(n) => fmt.write_fmt(format_args!("{}", n)),
}
}
}
fn main() {
let expr = Sum(BTreeSet::from([(1, Number(1)), (1, Product(BTreeSet::from([(1, Number(2)), (1, Variable(Box::from("x")))])))]));
println!("{:?}", expr);
println!("{}", expr);
assert_eq!(format!("{}", expr), String::from("x * 2 + 1"));
}
I have made the arguments of Sum
and Product
sets of (count, expr)
pairs, so that later mathematical manipulations like simplifying x + x + x + x + x to 5x, or x * y * z / y, will be easier (than having to search deeply-nested trees for equivalent expressions).