11
\$\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;
    }
};
\$\endgroup\$
  • 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 Nov 25 '16 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 Nov 25 '16 at 17:42
3
\$\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);
    }
    
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.