12
\$\begingroup\$

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;
 }
};
asked Nov 25, 2016 at 12:31
\$\endgroup\$
2
  • 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\$ Commented 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\$ Commented Nov 25, 2016 at 17:42

1 Answer 1

4
\$\begingroup\$

Here are some thoughts about your already quite nice code:

  1. use std::isspace for whitespace detection. In that case you can do

    str.erase(std::remove_if(str.begin(), str.end(), std::isspace), str.end());

  2. Don't use unnecessary abbreviations: bracks vs brackets is not really a gain at all.

  3. 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.

  4. 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);
    }
    
Null
1,4633 gold badges19 silver badges27 bronze badges
answered Nov 25, 2016 at 19:31
\$\endgroup\$
0

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.