简体   繁体   中英

What is the best way to add formulas and variables support in “Shunting Yard Algorithm”?

I am currently working at this project called "expression evaluator" and I've got some questions about how to code it.

First of all, what does this "expression evaluator" do? It simply evaluates the expression that you inserted, it must support:

  • round parentheses
  • operators: +, -, /, *, <>, =, <=, >=, or, and, xor, not, mod, \
  • all the mathematical functions from here
  • real, integer and logical constant (1, 1.1, -1, true, false, etc..)
  • normal variables like var, eg: 1 + var // the variables can be used like this: "var = 50 1 + var" and it should output 51.

I currently managed to transform the string to postfix notation(Reversed Polish Notation) and to evaluate it, but it currently supports only (+, -, *, /, ^) and only just the real and integer constants.

How could I add support for the rest of the operators/variables/formulas?

I have actually tried to add support for variables/formulas but it works only for basic expressions, like: pow(1, 3) + 2, this outputs 3 (TRUE). If I try more complex expressions it happens something like: pow(pow(2, 3), 2), this outputs 512 which is wrong, it should output 64.

Here's my code so you can test it yourself.

#include <algorithm>
#include <cmath>
#include <fstream>
#include <map>
#include <stack>
#include <string>
#include <vector>

#include <iostream>

// Setari
#define __DEBUG__ // Afiseaza informatii despre procesul de rezolvare.
#define __SUPPORT_DOUBLE__ // Activeaza suportul pentru numere reale.

// Variabile

std::vector<std::string> Infix;
std::stack<std::string> operators;
std::stack<std::string> Rezultat;

// Salvez operatorii si nivelul lor de prioritate intr-un dictioar pentru a face procesul de identificare mai rapid
std::map<std::string, short> Operators = { {"+", 1}, {"-", 1}, {"*", 2}, {"/", 2}, {"^", 3}, {"pow", 3}, {"==", 4}, {"=", 5} };

std::ifstream fin("eval.in");
std::ofstream fout("eval.out");


// Functii

// Caut in dictionarul 'Operators' operatorul si, daca-l gasesc returnez prioritatea acestuia.
int CheckOperator(const std::string& op){
    const auto& it = Operators.find(op);

    if (it != Operators.end())
        return it->second;

    return 0;
}

int main(){
    std::string expr, token;
    while (std::getline(fin, token))
        expr.append(token);
    fin.close();
    
    // Analizez expresie si sterg caracterele inutile( \ ).
    expr.erase(std::remove_if(expr.begin(), expr.end(), [](const char& c){ return c == '\\'; }), expr.end());
    // Transform expresia in expresie postfix.
    
    for (size_t i = 0; i < expr.length(); ++i){
        char c = expr[i];
        token.clear();

        int prioritate = 0;
        
        token += c;
        if (isdigit(c))
        {
            
            for (++i; i < expr.length(); ++i){
                c = expr[i];
#if defined(__SUPPORT_DOUBLE__)
                if (!isdigit(c) && c != '.')
                    break;
#else
                if (!isdigit(c))
                    break;
#endif
                token += c;
            }
            --i;
            Infix.push_back(token);
        }
        else if(isalpha(c))
        {
            for (++i; i < expr.length(); ++i){
                c = expr[i];

                if (!isalnum(c))
                    break;
                
                token += c;
            }
            std::cout << "Verificare: " << token << '\n';
            
            --i;

            if (CheckOperator(token))
                operators.push(token);
            else
                Infix.push_back(token);
        }
        else if((prioritate = CheckOperator(token))){
            bool numar = false;
            for (++i; i < expr.length(); ++i){
                c = expr[i];
                token += c;
#if defined(__SUPPORT_DOUBLE__)
                if (!CheckOperator(token) && !isdigit(c) && c != '.'){
                    token.erase(token.length() - 1, token.length());
                    break;
                }
#else
                if (!CheckOperator(token) && !isdigit(c)){
                    token.erase(token.length() - 1, token.length());
                    break;
                }
#endif
                if (isdigit(c))
                    numar = true;
            }
            --i;
            if (!numar){
                while(!operators.empty() && CheckOperator(operators.top()) >= prioritate)
                {
                    Infix.push_back(operators.top());
                    operators.pop();
                }
                operators.push(token);
            }
            else
            {
                Infix.push_back(token);
            }
            
        }
        else if (c == '(')
        {
            operators.push("(");
        }
        else if (c == ')')
        {
            while (!operators.empty())
            {
                if (operators.top() == "("){
                    operators.pop();
                    break;
                }
                Infix.push_back(operators.top());
                operators.pop();
            }
        }
    }
    while (!operators.empty())
    {
        Infix.push_back(operators.top());
        operators.pop();

    }

#if defined(__DEBUG__)
    for (const auto& ex : Infix)
        fout << ex << ' ';
    fout << '\n';
#endif

    auto& it = Infix.begin();
    for(; it != Infix.end(); ++it)
    {
        if (CheckOperator(*it))
        {
            if (*it == "+")
            {
                long double b = std::stold(Rezultat.top());
                Rezultat.pop();
                long double a = std::stold(Rezultat.top());
                Rezultat.pop();

                Rezultat.push(std::to_string(a + b));
            }
            else if(*it == "-")
            {
                long double b = std::stold(Rezultat.top());
                Rezultat.pop();
                long double a = std::stold(Rezultat.top());
                Rezultat.pop();

                Rezultat.push(std::to_string(a - b));
            }
            else if(*it == "*")
            {
                long double b = std::stold(Rezultat.top());
                Rezultat.pop();
                long double a = std::stold(Rezultat.top());
                Rezultat.pop();

                Rezultat.push(std::to_string(a * b));
            }
            else if(*it == "/")
            {
                long double b = std::stold(Rezultat.top());
                Rezultat.pop();
                long double a = std::stold(Rezultat.top());
                Rezultat.pop();

                Rezultat.push(std::to_string(a / b));
            }
            else if(*it == "^")
            {
                long double b = std::stold(Rezultat.top());
                Rezultat.pop();
                long double a = std::stold(Rezultat.top());
                Rezultat.pop();

                Rezultat.push(std::to_string(std::powl(a, b)));
            }
            else if(*it == "==")
            {
                long double b = std::stold(Rezultat.top());
                Rezultat.pop();
                long double a = std::stold(Rezultat.top());
                Rezultat.pop();

                Rezultat.push(std::to_string(a == b));
            }
            else if(*it == "pow")
            {
                long double b = std::stold(Rezultat.top());
                Rezultat.pop();
                long double a = std::stold(Rezultat.top());
                Rezultat.pop();

                Rezultat.push(std::to_string(std::powl(a, b)));
            }
        }
        else
        {
            Rezultat.push(*it);
        }
    }
#if defined(__SUPPORT_DOUBLE__)
    fout << Rezultat.top() << '\n';
#else
    fout << std::stol(Rezultat.top()) << '\n';
#endif
    return 0;
}

Thank you!

Edit: I have posted a solution on Github if you need it.

It isn't quite clear what you mean by "formulas".

Variables in the shunting yard algorithm behave in exactly the same way as numbers, no difference whatsoever.

An assignment x = y can be treated as just another kind of expression. You want to add the = operator to your list of supported operators. Just choose its precedence.

Obviously the evaluator needs to distinguish variables from constants and expressions, so that it can signal an error in each of these cases:

    x + 2 (undefined x)
    2 = 5 (assignment to non-variable)
(1+2) = 3 (same)

Note that you want to be able to evaluate several expressions now, so that you may evaluate var = 50 and then 1 + var as two separate expressions. Devise a way to separate them. The ; character will work just fine for this purpose.

Also note that having = to serve for both comparison and assignment is really really not advised. You want to either change the comparison to == as most programming languages do these days, or change the assignment to use some other symbol, say := or <- .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM