patterncppMinor
Polish notation interpreter/calculator
Viewed 0 times
interpretercalculatornotationpolish
Problem
Just for the heck of it, I wrote a simple little interpreter that takes in inputs in the form of Polish Notation.
The user will input something like
And it will then output
Here's another sample input:
And the output:
Users can even define their own variables, like this:
and like this:
And then of course reuse those variables. So let's say you type
it will ouput
or
which outputs
Furthermore, you type in
you would then get
I'm a total newbie to well-formatted and readable code. What would be a good way to refactor this?
```
#include
#include
#include
#include
using namespace std;
class Variable
{
public:
Variable(const string& name, double val)
{
this->name = name;
this->val = val;
}
inline const string& get_name() const { return name; }
inline double get_val() const { return val; }
private:
string name;
double val;
};
//----------------------------------
double operate(const string& op, istringstream& iss, vector& v);
double perform_addition(istringstream& iss, vector& v);
double perform_subtraction(istringstream& iss, vector& v);
double perform_division(istringstream& iss, vector& v);
double perform_multiplication(istringstream& iss, vector& v);
void define_new_var(vector& v, istringstream& iss);
bool is_number(const string& op)
{
int char_to_int = op[0];
if (char_to_int >= 48 && char_to_int & v)
{
int size = v.size();
for (int i = 0; i v;
string temp;
while (cin)
{
cout ";
getline(cin, temp);
istringstream iss(temp);
string op;
iss >> op;
if (op == "quit")
break;
else if (op == "def")
define_new_var(v, iss);
else if (op == "show_vars")
print_var_list(v);
else
cout & v)
{
The user will input something like
+ 5 6And it will then output
11Here's another sample input:
+ 5 * 2 3And the output:
11Users can even define their own variables, like this:
def myVar 10and like this:
def anotherVar + 5 6And then of course reuse those variables. So let's say you type
myVarit will ouput
10or
anotherVarwhich outputs
11Furthermore, you type in
+ myVar anotherVaryou would then get
21I'm a total newbie to well-formatted and readable code. What would be a good way to refactor this?
```
#include
#include
#include
#include
using namespace std;
class Variable
{
public:
Variable(const string& name, double val)
{
this->name = name;
this->val = val;
}
inline const string& get_name() const { return name; }
inline double get_val() const { return val; }
private:
string name;
double val;
};
//----------------------------------
double operate(const string& op, istringstream& iss, vector& v);
double perform_addition(istringstream& iss, vector& v);
double perform_subtraction(istringstream& iss, vector& v);
double perform_division(istringstream& iss, vector& v);
double perform_multiplication(istringstream& iss, vector& v);
void define_new_var(vector& v, istringstream& iss);
bool is_number(const string& op)
{
int char_to_int = op[0];
if (char_to_int >= 48 && char_to_int & v)
{
int size = v.size();
for (int i = 0; i v;
string temp;
while (cin)
{
cout ";
getline(cin, temp);
istringstream iss(temp);
string op;
iss >> op;
if (op == "quit")
break;
else if (op == "def")
define_new_var(v, iss);
else if (op == "show_vars")
print_var_list(v);
else
cout & v)
{
Solution
#include
#include
#include
#include
#include // mmocny: I needed to add this to use atof
#include
using namespace std;
//----------------------------------
class Variable
{
public:
Variable(const string& name, double val)
: name_(name), val_(val) // mmocny: Use initializer lists
{
}
// mmocny: get_* syntax is less common in C++ than in java etc.
const string& name() const { return name_; } // mmocny: Don't mark as inline (they already are, and its premature optimization)
double val() const { return val_; } // mmocny: Again, don't mark as inline
private:
string name_; // mmocny: suggest renaming name_ or _name: Easier to spot member variables in method code, and no naming conflicts with methods
double val_;
};
//----------------------------------
// mmocny: Replace print_* methods with operator const & v)
{
for (vector::const_iterator it = v.begin(), end = v.end(); it != end; ++it ) // mmocny: Use iterators rather than index access
{
out & v)
{
// mmocny: instead of using a vector you should be using a map/unordered_map and doing a key lookup here
int size = v.size();
for (int i = 0; i = '0' && char_to_int & v);
//----------------------------------
/*
* mmocny: All of your perform_* functions have identical code other than the operator being used.
* You can turn all of these functions into a single function template where you pass the operator to be used.
* Luckily, already has plus minus multiplies divides function objects (functors)
*/
template
double perform_action(istream& in, vector& v, Operator op)
{
string left;
in >> left;
double result = operate(left, in, v); // mmocny: This is a big one: for correctness, you must calculate result of left BEFORE you read right
string right;
in >> right;
return op(result, operate(right, in, v));
}
//----------------------------------
double operate(const string& op, istream& in, vector& v)
{
double value;
if (op == "+")
value = perform_action(in, v, plus());
else if (op == "-")
value = perform_action(in, v, minus());
else if(op == "*")
value = perform_action(in, v, multiplies());
else if (op == "/")
value = perform_action(in, v, divides());
else if (is_number(op))
value = atof(op.c_str()); // mmocny: consider using boost::lexical_cast<>, or strtod (maybe)
else
value = get_variable(op, v);
return value;
}
//----------------------------------
void define_new_var(vector& v, istream& in)
{
string name;
in >> name;
string temp;
in >> temp;
double value = operate(temp, in, v);
v.push_back(Variable(name, value));
}
//----------------------------------
int main()
{
cout v;
string temp;
while (cin)
{
cout ";
getline(cin, temp);
if (temp.empty()) // mmocny: This also handles the case of CTRL+D
continue;
istringstream iss(temp);
string op;
iss >> op;
if (op == "quit")
break;
else if (op == "def")
define_new_var(v, iss);
else if (op == "show_vars")
std::cout << v << std::endl;
else
cout << endl << operate(op, iss, v) << endl;
}
}Those are my changes, with comments inline. I would make more changes, but thats enough for now :)
Notice one BIG change: you have a serious correctness bug in your perform_* functions. Not that I've tested my modified code above for all edge cases, but the original code was flat out always wrong for any nested calculations.
Code Snippets
#include <iostream>
#include <vector>
#include <string>
#include <sstream>
#include <cstdlib> // mmocny: I needed to add this to use atof
#include <functional>
using namespace std;
//----------------------------------
class Variable
{
public:
Variable(const string& name, double val)
: name_(name), val_(val) // mmocny: Use initializer lists
{
}
// mmocny: get_* syntax is less common in C++ than in java etc.
const string& name() const { return name_; } // mmocny: Don't mark as inline (they already are, and its premature optimization)
double val() const { return val_; } // mmocny: Again, don't mark as inline
private:
string name_; // mmocny: suggest renaming name_ or _name: Easier to spot member variables in method code, and no naming conflicts with methods
double val_;
};
//----------------------------------
// mmocny: Replace print_* methods with operator<< so that other (non cout) streams can be used.
// mmocny: Alternatively, define to_string()/str() methods which can also be piped out to different streams
std::ostream & operator<<(std::ostream & out, Variable const & v)
{
return out << v.name() << ", " << v.val() << endl;
}
std::ostream & operator<<(std::ostream & out, vector<Variable> const & v)
{
for (vector<Variable>::const_iterator it = v.begin(), end = v.end(); it != end; ++it ) // mmocny: Use iterators rather than index access
{
out << *it << endl;
}
return out;
}
//----------------------------------
double get_variable(const string& op, vector<Variable>& v)
{
// mmocny: instead of using a vector<Variable> you should be using a map/unordered_map<string,double> and doing a key lookup here
int size = v.size();
for (int i = 0; i < size; i++)
{
if (op == v[i].name())
return v[i].val();
}
// mmocny: what do you do if you don't find the variable?
throw std::exception(); // mmocny: You should do something better than throw a generic exception()
}
//----------------------------------
bool is_number(const string& op)
{
// mmocny: someone else already mentioned: what if op is empty?
int char_to_int = op[0];
// mmocny: couple notes here:
// 1) chars are actually numbers you can reference directly, and not need "magic" constants
// 2) functions in the form "if (...) return true; else return false;" should just be reduced to "return (...);"
// 3) is_number functionality already exists in libc as isdigit()
// 4) long term, you are probably going to want to improve this function.. what about negative numbers? numbers in the form .02? etc..
//return (char_to_int >= '0' && char_to_int <= '9');
return isdigit(char_to_int);
}
//----------------------------------
// mmocny: replace istringstream with istream
// mmocny: you only need to predeclare this one function
double operate(const string& op, istream& in, vector<Variable>& v);
//----------------------------------
/*
* mmocny: All of yourContext
StackExchange Code Review Q#2211, answer score: 5
Revisions (0)
No revisions yet.