patternrustMinor
Parsing/RPN calculator algorithm
Viewed 0 times
algorithmcalculatorparsingrpn
Problem
I decided to try and complete the Parsing/RPN calculator algorithm from Rosetta Code in Rust. I'm still learning Rust and would appreciate feedback on my code.
I had attempted to write the commutative operations without assigning the operands to variables (i.e.
However, I received:
Another Approach
One approach I noticed some of the other languages' implementations using was to first check if a token is a number and push it onto the stack before checking for the operations. This allowed them to pop the two operands for the operations after they we
use std::str::FromStr;
fn rpn(expression: &str) -> f64 {
let mut stack: Vec = Vec::new();
let tokens: Vec = expression.split_whitespace().collect();
for token in tokens {
match token {
"+" => {
let b = stack.pop().unwrap();
let a = stack.pop().unwrap();
stack.push(a + b);
},
"-" => {
let b = stack.pop().unwrap();
let a = stack.pop().unwrap();
stack.push(a - b);
},
"*" => {
let b = stack.pop().unwrap();
let a = stack.pop().unwrap();
stack.push(a * b);
},
"/" => {
let b = stack.pop().unwrap();
let a = stack.pop().unwrap();
stack.push(a / b);
},
"^" => {
let b = stack.pop().unwrap();
let a = stack.pop().unwrap();
stack.push(a.powf(b));
},
_ => {
stack.push(f64::from_str(token).unwrap());
}
}
}
stack.pop().unwrap()
}
fn main() {
let expression = "3 4 2 * 1 5 - 2 3 ^ ^ / +";
println!("{}", rpn(expression));
}I had attempted to write the commutative operations without assigning the operands to variables (i.e.
a and b) like this:stack.push(stack.pop().unwrap() + stack.pop().unwrap());However, I received:
error: cannot borrow `stack` as mutable more than once at a time [E0499]Another Approach
One approach I noticed some of the other languages' implementations using was to first check if a token is a number and push it onto the stack before checking for the operations. This allowed them to pop the two operands for the operations after they we
Solution
Overall, your code is pretty readable and understandable.
That's the basic style stuff, looking up a level...
There's a lot of repeated code for the 5 binary operators. I'd suggest refactoring these out:
There's also a lot of
Rust has a built-in testing framework, so I'd encourage you to use it. It's easy enough to throw in some tests instead of visually comparing the output of
I had attempted to write the commutative operations without assigning the operands to variables
Yes, this is a current limitation of the borrow checker. The entire expression is borrow checked at once, and it sees that
There is work in progress to loosen up these checks, but separate variables is the current solution.
For the alternate code:
- There is an unneeded type on
tokens; it can just beVec.
- There's no need to
collectto a vector at all! The current code takes an iterator, stores it in a vector, then converts it back into an iterator. Instead, just use the first iterator.
- It's unusual to use
from_strdirectly. Usually, you callparseon the string.
That's the basic style stuff, looking up a level...
There's a lot of repeated code for the 5 binary operators. I'd suggest refactoring these out:
struct Stack(Vec);
impl Stack {
fn new() -> Stack { Stack(Vec::new()) }
fn pop(&mut self) -> Option {
self.0.pop()
}
fn parse(&mut self, token: &str) {
self.0.push(token.parse().unwrap());
}
fn binary(&mut self, f: F)
where F: Fn(f64, f64) -> f64
{
let b = self.0.pop().unwrap();
let a = self.0.pop().unwrap();
self.0.push(f(a, b));
}
}
fn rpn(expression: &str) -> f64 {
let mut stack = Stack::new();
for token in expression.split_whitespace() {
match token {
"+" => stack.binary(|a, b| a + b),
"-" => stack.binary(|a, b| a - b),
"*" => stack.binary(|a, b| a * b),
"/" => stack.binary(|a, b| a / b),
"^" => stack.binary(|a, b| a.powf(b)),
_ => stack.parse(token),
}
}
stack.pop().unwrap()
}
fn main() {
let expression = "3 4 2 * 1 5 - 2 3 ^ ^ / +";
println!("{}", rpn(expression));
}There's also a lot of
unwrap calls. This is fine for quick code that you don't expect to have non-programmers using, but often it's a good idea to try and see how error reporting can work:use std::{error, fmt, num};
#[derive(Debug)]
enum Error {
InvalidNumber(num::ParseFloatError),
EmptyStack,
}
impl error::Error for Error {
fn description(&self) -> &str {
match *self {
Error::InvalidNumber(..) => "Not a valid number",
Error::EmptyStack => "No numbers on the stack",
}
}
fn cause(&self) -> Option {
match *self {
Error::InvalidNumber(ref e) => Some(e),
Error::EmptyStack => None,
}
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", error::Error::description(self))
}
}
impl From for Error {
fn from(e: num::ParseFloatError) -> Error { Error::InvalidNumber(e) }
}
struct Stack(Vec);
impl Stack {
fn new() -> Stack { Stack(Vec::new()) }
fn pop(&mut self) -> Option {
self.0.pop()
}
fn parse(&mut self, token: &str) -> Result {
self.0.push(try!(token.parse()));
Ok(())
}
fn binary(&mut self, f: F) -> Result
where F: Fn(f64, f64) -> f64
{
let b = try!(self.0.pop().ok_or(Error::EmptyStack));
let a = try!(self.0.pop().ok_or(Error::EmptyStack));
self.0.push(f(a, b));
Ok(())
}
}
fn rpn(expression: &str) -> Result {
let mut stack = Stack::new();
for token in expression.split_whitespace() {
let r = match token {
"+" => stack.binary(|a, b| a + b),
"-" => stack.binary(|a, b| a - b),
"*" => stack.binary(|a, b| a * b),
"/" => stack.binary(|a, b| a / b),
"^" => stack.binary(|a, b| a.powf(b)),
_ => stack.parse(token),
};
try!(r);
}
stack.pop().ok_or(Error::EmptyStack)
}
fn main() {
let expression = "3 4 2 * 1 5 - 2 3 ^ ^ / +";
println!("{:?}", rpn(expression));
}Rust has a built-in testing framework, so I'd encourage you to use it. It's easy enough to throw in some tests instead of visually comparing the output of
main. I've wrapped the tests in a bit of ceremony here to ensure they aren't compiled during a production build:// Add `PartialEq` to the `derive` on `Error` to allow comparison
#[cfg(test)]
mod test {
use super::rpn;
#[test]
fn golden_master() {
assert_eq!(Ok(3.0001220703125), rpn("3 4 2 * 1 5 - 2 3 ^ ^ / +"));
}
}I had attempted to write the commutative operations without assigning the operands to variables
Yes, this is a current limitation of the borrow checker. The entire expression is borrow checked at once, and it sees that
stack.push requires a mutable borrow and stack.pop requires an immutable borrow. Since you can't have any other borrows during a mutable borrow, you get the error. It isn't currently smart enough to understand that the immutable borrow ends before the mutable borrow begins.There is work in progress to loosen up these checks, but separate variables is the current solution.
For the alternate code:
- There's no need for an explicit type on
parse. The fact that it needs to be af64can be inferred.
- Use an
if letexpression instead ofif_ok. This avoids the need tounwrapand better communicates intent.
- Having a
Code Snippets
struct Stack(Vec<f64>);
impl Stack {
fn new() -> Stack { Stack(Vec::new()) }
fn pop(&mut self) -> Option<f64> {
self.0.pop()
}
fn parse(&mut self, token: &str) {
self.0.push(token.parse().unwrap());
}
fn binary<F>(&mut self, f: F)
where F: Fn(f64, f64) -> f64
{
let b = self.0.pop().unwrap();
let a = self.0.pop().unwrap();
self.0.push(f(a, b));
}
}
fn rpn(expression: &str) -> f64 {
let mut stack = Stack::new();
for token in expression.split_whitespace() {
match token {
"+" => stack.binary(|a, b| a + b),
"-" => stack.binary(|a, b| a - b),
"*" => stack.binary(|a, b| a * b),
"/" => stack.binary(|a, b| a / b),
"^" => stack.binary(|a, b| a.powf(b)),
_ => stack.parse(token),
}
}
stack.pop().unwrap()
}
fn main() {
let expression = "3 4 2 * 1 5 - 2 3 ^ ^ / +";
println!("{}", rpn(expression));
}use std::{error, fmt, num};
#[derive(Debug)]
enum Error {
InvalidNumber(num::ParseFloatError),
EmptyStack,
}
impl error::Error for Error {
fn description(&self) -> &str {
match *self {
Error::InvalidNumber(..) => "Not a valid number",
Error::EmptyStack => "No numbers on the stack",
}
}
fn cause(&self) -> Option<&error::Error> {
match *self {
Error::InvalidNumber(ref e) => Some(e),
Error::EmptyStack => None,
}
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", error::Error::description(self))
}
}
impl From<num::ParseFloatError> for Error {
fn from(e: num::ParseFloatError) -> Error { Error::InvalidNumber(e) }
}
struct Stack(Vec<f64>);
impl Stack {
fn new() -> Stack { Stack(Vec::new()) }
fn pop(&mut self) -> Option<f64> {
self.0.pop()
}
fn parse(&mut self, token: &str) -> Result<(), Error> {
self.0.push(try!(token.parse()));
Ok(())
}
fn binary<F>(&mut self, f: F) -> Result<(), Error>
where F: Fn(f64, f64) -> f64
{
let b = try!(self.0.pop().ok_or(Error::EmptyStack));
let a = try!(self.0.pop().ok_or(Error::EmptyStack));
self.0.push(f(a, b));
Ok(())
}
}
fn rpn(expression: &str) -> Result<f64, Error> {
let mut stack = Stack::new();
for token in expression.split_whitespace() {
let r = match token {
"+" => stack.binary(|a, b| a + b),
"-" => stack.binary(|a, b| a - b),
"*" => stack.binary(|a, b| a * b),
"/" => stack.binary(|a, b| a / b),
"^" => stack.binary(|a, b| a.powf(b)),
_ => stack.parse(token),
};
try!(r);
}
stack.pop().ok_or(Error::EmptyStack)
}
fn main() {
let expression = "3 4 2 * 1 5 - 2 3 ^ ^ / +";
println!("{:?}", rpn(expression));
}// Add `PartialEq` to the `derive` on `Error` to allow comparison
#[cfg(test)]
mod test {
use super::rpn;
#[test]
fn golden_master() {
assert_eq!(Ok(3.0001220703125), rpn("3 4 2 * 1 5 - 2 3 ^ ^ / +"));
}
}fn rpn(expression: &str) -> f64 {
let mut stack: Vec<f64> = Vec::new();
for token in expression.split_whitespace() {
if let Ok(value) = token.parse() {
stack.push(value);
continue;
}
let b = stack.pop().unwrap();
let a = stack.pop().unwrap();
match token {
"+" => stack.push(a + b),
"-" => stack.push(a - b),
"*" => stack.push(a * b),
"/" => stack.push(a / b),
"^" => stack.push(a.powf(b)),
other => panic!("Unknown symbol `{}`", other),
}
}
stack.pop().unwrap()
}Context
StackExchange Code Review Q#122566, answer score: 5
Revisions (0)
No revisions yet.