It is my second attempt to make a math expression evaluator.
To evaluate an expression, I created a class that works like this:
-it removes all the blank spaces
-if brackets are present, it processes them by creating another evaluator with the expression inside those brackets
-it gets all the numbers and does first all the exponentiations, then the multiplications/divisions and finally sums all the numbers
For example, let's say I have the expression $3ドル*(3+4)^2+3*(5+6)$$
-replaces (3+4) and (5+6) with 7 and 11 $3ドル*7^2+3*11$$
-does 7^2 $3ドル*49+3*11$$
-does the multiplications $147ドル+33$$
-sums all the numbers $180ドル$$
If you know if there's something wrong or to optimize I'd really appreciate it
MathSolver.h
#include <vector>
#include <string>
#include <algorithm>
#include <sstream>
#include <iostream>
#include <map>
#include <functional>
void changeCharacters(std::string chars, std::string toChange, std::string &str)
{
int i = -1;
std::for_each(str.begin(), str.end(), [chars, toChange, &i, &str](char c)
{
i++;
for (auto &j : chars)
{
if (c == j)
{
str.erase(str.begin() + i);
str.insert(i, toChange);
break;
}
}
});
}
class MathSolver
{
private:
std::string exp;
std::vector<double> nums;
double result;
std::function<bool(char)> isPlusMinus = [](char c) {return c == '+' || c == '-'; };
std::function<bool(char)> isMultDiv = [](char c) {return c == '*' || c == '/'; };
public:
MathSolver(std::string exp)
{
exp.erase(std::remove(exp.begin(), exp.end(), ' '), exp.end());
if (exp.find_first_of("+-") != 0)
exp.insert(0, "+");
for (int i = 0; i < exp.length(); i++)
{
if (isPlusMinus(exp[i]))
exp.insert(i + 1, "1*");
}
changeCharacters("[{", "(", exp);
changeCharacters("]}", ")", exp);
changeCharacters(":", "/", exp);
this->exp = exp;
this->processBrackets();
this->parse();
}
void countBracks(std::vector< std::pair<int, int>> &bracks)
{
int parOC = 0;
for (int i = 0; i < exp.length(); i++)
{
if (exp[i] == '(')
{
if (parOC == 0)
bracks.push_back(std::make_pair(i, 0));
parOC++;
}
else if (exp[i] == ')')
{
parOC--;
if (parOC == 0)
bracks[bracks.size() - 1].second = i;
}
}
}
void processBrackets()
{
std::vector< std::pair<int, int>> bracks;
countBracks(bracks);
int count = bracks.size();
for (int i = 0; i < count; i++)
{
std::pair<int, int> j = bracks[0];
MathSolver solve(exp.substr(j.first + 1, j.second - 1 - j.first));
double res = solve.getResult();
std::stringstream ss;
ss << res;
exp.erase(exp.begin() + j.first, exp.begin() + j.second + 1);
exp.insert(j.first, ss.str());
bracks.clear();
countBracks(bracks);
}
}
void parse()
{
std::function<void(double&, std::istringstream&)> searchPow = [](double &num, std::istringstream &iss)
{
if (iss.peek() == '^')
{
char tmp2;
double tmp3;
iss >> tmp2 >> tmp3;
num = pow(num, tmp3);
}
};
double num;
char tmp;
std::istringstream iss(exp);
while ((int)iss.tellg() != EOF)
{
if (isPlusMinus(iss.peek()) && isdigit(exp[(int)iss.tellg() + 1]))
{
iss >> num;
searchPow(num, iss);
nums.push_back(num);
}
else if (isMultDiv(iss.peek()) && (isdigit(exp[(int)iss.tellg() + 1]) || isdigit(exp[(int)iss.tellg() + 2])))
{
iss >> tmp >> num;
searchPow(num, iss);
nums.push_back(num);
if (tmp == '/')
nums[nums.size() - 1] = 1 / nums[nums.size() - 1];
nums[nums.size() - 1] *= nums[nums.size() - 2];
nums[nums.size() - 2] = 0;
}
}
nums.erase(remove(nums.begin(), nums.end(), 0), nums.end());
for (auto i : nums)
result += i;
}
double getResult()
{
return result;
}
};
-
4\$\begingroup\$ There is an entire field in computer science dedicated to this, i. e. to formally describe the syntax of expressions or entire languages and to implement its parsing, execution and/or compilation. The usual approach is define the syntax, implement a tokenizer (splits string into the numbers and operators) and implement the parsing (checks syntax and immediately evaluate expression). The easiest to understand parser is the recursive descent parser. \$\endgroup\$Codo– Codo2016年11月25日 12:43:29 +00:00Commented Nov 25, 2016 at 12:43
-
1\$\begingroup\$ You can also use the shunting yard algorithm, which is specifically designed for such math expressions. It might be easier to build a fully fledged parser :) \$\endgroup\$Rakete1111– Rakete11112016年11月25日 17:42:30 +00:00Commented Nov 25, 2016 at 17:42
1 Answer 1
Here are some thoughts about your already quite nice code:
use
std::isspace
for whitespace detection. In that case you can dostr.erase(std::remove_if(str.begin(), str.end(), std::isspace), str.end());
Don't use unnecessary abbreviations: bracks vs brackets is not really a gain at all.
You should use a
std::stack
for bracket checking. So whenever you encounter an opening bracket you put it on the stack and check the next one.You might want to get a separate function for extraction of the inner bracket content such like this:
std:string innerBracketString(const std::string& expr, size_t& startPos, size_t& endPos) { startPos = expr.find_first_of("([{", endPos)+1; endPos = expr.find_first_of(")]}", startPos); return expr.substr(startPos, endPos - startPos); }
Explore related questions
See similar questions with these tags.