c++ - 无法解析不同的数学运算符

标签 c++ parsing recursion

这个问题是 this one 的后续问题。 .基本上我正在尝试制作一个解析器来计算字符串的总结果。 5+5+3*2/1应该给16 .这已经适用于仅包含加号和分钟的字符串,所以 -55-44+1-2+123-54442+327737+1-2成功给273317 .

但是,当 plusses/mins 与 time/divides 混合时,它就不起作用了。所以1*2-2*3返回 6而不是 -4 .我认为这是因为我试图尊重数学需要执行的顺序(第一个加号和分钟,而不是时间和除法),但操作符不知何故没有得到更新。

#include <iostream>
#include <string>
#include <algorithm>

//Enumeration of all the possible
//math operators
enum Operator {
    PLUS,
    MIN,
    TIMES,
    DIVIDE,
    UNDEFINED
};




/************************IGNORE********************/
    char operatorToChar(Operator o) {
        switch(o) {
            case Operator::PLUS:
                return '+';
                break;
            case Operator::MIN:
                return '-';
                break;
            case Operator::TIMES:
                return '*';
                break;
            case Operator::DIVIDE:
                return '/';
                break;
            default:
                return '0';
                break;
        }
    }
/***************************************************/

/*
 * Function to check if there are still times- or divide-operators in the action string.
 * This to respect the order of math (first times and divides, than plusses and mins)
 *
 * :param action: The action string
 * :return bool: Returns true if a '*' or '/' is found
 */
bool timesAndDividesGone(std::string& action) {
    for (char& c : action) {
        if (c == '*' || c == '/') {
            return false;
        }
    }

    return true;
}


/*
 * Function to convert char to Operator
 * :param c: One of the following '+', '-', '*', '/'
 * :return Operator: Operating matching the character
 */
Operator charToOperator(char c) {
    switch(c) {
        case '+':
            return Operator::PLUS;
            break;
        case '-':
            return Operator::MIN;
            break;
        case '*':
            return Operator::TIMES;
            break;
        case '/':
            return Operator::DIVIDE;
            break;
        default:
            return Operator::UNDEFINED;
            break;
    }
}

/*
 * Function to do maths on two numbers, the math to do is decided by the operator
 * :param x: First number
 * :param y: Second number
 * :param o: Operator (Plus, Min, Times or Divide) 
 * :return double: Result of the calculation
 *
 * Example:
 * math(5, 5, Operator::Plus) == 10
 *
 */
double math(double x, double y, Operator o) {
    double z = 0;

    switch (o) {
        case Operator::PLUS:
            z = x + y;
            break;
        case Operator::MIN:
            z = x - y;
            break;
        case Operator::TIMES:
            z = x * y;
            break;
        case Operator::DIVIDE:
            z = x / y;
            break;
    }

    return z;
}

/*
 * Recursive function performing all the calculations from an action string.
 * For example, if the string actions has value "5+7" in the first recursive run
 * result should contain 12 after the last recursion.
 *
 * :param result: Double containing the calculated result after the last recursion
 * :param actions: Action string (what you type in your calculator; e.g: 5+5). We analyze the first character of this string each time and add it to first_nr, second_nr, or make it the operator. First character gets deleted after each recursion
 * :param first_nr: Empty at first recursion, number of left side of the operator. So in 55+77 this paramater will be "55". Gets resetted at the next operator
 * :param second_nr: Idem as first_nr but for the right side of the operator.
 * :param oper: Operation to calculate the first_nr and second_nr
 */
double calculate(double& result, std::string& actions, std::string& first_nr, std::string& second_nr, Operator& oper) {


    //DEBUG OUTPUT:
    std::cout << actions << " Gives ";
    std::cout << std::to_string(result) << std::endl;


    //Base-condition:
    //If action string is empty return 
    if (actions == "") {

        //Scenario for when first action is an operator
        //e.g: 1+1-
        if (second_nr == "")
            second_nr = "0";

        //Update result
        result = math(std::stod(first_nr), std::stod(second_nr), oper);

        return result;
    }



    //Get first character from action string
    char c = actions[0];

    //Making sure order of math is respected (first times and divdes)
    //and than plus and min
    char operatorInChar[4] = {'*', '/'};
    if (timesAndDividesGone(actions)) {
        operatorInChar[2] = '+';
        operatorInChar[3] = '-';
    }

    //If first character is an operator
    if (std::find(std::begin(operatorInChar), std::end(operatorInChar), c) != std::end(operatorInChar)) {

        //Scenario for when first action is an operator
        //e.g: -1+1
        if (first_nr == "") {
            if (actions[1] == '*')
                first_nr = "1";
            else
                first_nr = "0";
        }

        //If operator is not yet set in a previous recursion
        if (oper == Operator::UNDEFINED) {
            oper = charToOperator(c);

            //If second_nr is not empty, we need to calculate the two numbers together
            if (second_nr != "") {
                //Update result
                result = math(std::stod(first_nr), std::stod(second_nr), oper);
            } 
        } else {
            //Update result
            result = math(std::stod(first_nr), std::stod(second_nr), oper);

            first_nr = std::to_string(result);
            second_nr = "";

            //Remove first character from action string because it's analysed in this recursion
            actions = actions.erase(0, 1);
            oper = charToOperator(c);
            return calculate(result, actions, first_nr, second_nr, oper);

        }

    } else {
        //If the character is not a operator but a number we append it to the correct nr
        //we add to first_nr if the operator is not yet set, if we already encountered an operator
        //we add to second_nr.
        //e.g: actions = "123+789"

        if (oper == Operator::UNDEFINED) {
            first_nr += c;
        } else {
            second_nr += c;
        }

    }

    //Remove first character from action string because it's analysed in this recursion
    actions = actions.erase(0, 1);

    //DEBUG OUTPUT:
    //std::cout << first_nr << operatorToChar(oper) << second_nr << std::endl;
    //std::cout << std::endl << actions << " Gives ";
    //std::cout << std::to_string(result) << std::endl;

    //Make recursive call
    return calculate(result, actions, first_nr, second_nr, oper);
}

int main() {
    //String we want to calculate
    std::string str = "1*2-2*3";
    std::string str_copy_for_output = str;

    //Variables
    double result = 0;
    std::string first_nr = "";
    std::string second_nr = "";
    Operator oper = Operator::UNDEFINED;

    //Call function
    int calculation = calculate(result, str, first_nr, second_nr, oper);

    //Output
    std::cout << std::endl << str_copy_for_output << " = " << calculation << std::endl;

    return 0;
}


tl;博士 此代码非常适用于仅包含加号和分钟或仅包含时间和除数的字符串。结合时间和划分把它搞砸了。可能 operator 参数无法更新。如何解决这个问题?

最佳答案

很抱歉,如果我没有详 segmentation 析您的代码,因为它对于您想要做的事情来说太复杂了。因此,我不会告诉你问题到底出在哪里,而是我会建议你一些更简单的东西。

您需要以一种或另一种方式管理堆栈,因为代数表达式必须作为树结构处理,并且评估过程必须遵循该结构。它不能作为平面结构来处理,也无法逃脱运算符优先级的管理。除此之外,表达式通常是从左到右计算的(左结合性)。

也就是说,如果您真的不想使用解析工具(恕我直言,它会更简单和干净),总是可以“手动”解析。在这种情况下,您可以避免使用调用堆栈本身来管理显式堆栈,如以下代码所示:

#include <iostream>

int precedenceOf(char op) {
    switch (op) {
    case '+':
    case '-':
        return 4;
    case '*':
    case '/':
        return 3;
    }
    return 0;   // never happen
}
const int MAX_PRECEDENCE = 4;

double computeOp(double left, double right, char c) {
    switch (c) {
    case '+': return left + right;
    case '-': return left - right;
    case '*': return left * right;
    case '/': return left / right;
    }
    return 0;   // never happen
}

char readOperator(const char*& expr)
{
    // read the operator
    while (*expr != 0) {
        switch (*expr) {
        case '+':
        case '-':
        case '*':
        case '/':
        {
            char res = *expr;
            expr++;
            return res;
        }
        case ' ':
            break;
        }
        expr++;
    }
    return 0;
}

double readOperand(const char*& expr)
{
    double result = 0;
    while (*expr != 0 && *expr == ' ') expr++;
    while (*expr != 0) {
        if (*expr >= '0' && *expr <= '9')
            result = result * 10 + *expr - '0';
        else
            return result;
        expr++;
    }
    return result;
}

double eval(const char*& expr, int breakPrecedence = MAX_PRECEDENCE + 1);

// evalRight function reads the right part of an expression and evaluates it 
// (up to the point where an operator with precedence 'breakPrecedence' is reached)
// returns the computation of the expression with the left operand passed as parameter.
double evalRight(const char*& expr, int breakPrecedence, double leftOperand)
{
    do
    {
        auto posBeforeOp = expr;
        auto op = readOperator(expr);
        if (op == 0)
            return leftOperand;  // end of expression reached, meaning there is no right part

        auto prec = precedenceOf(op);
        if (prec >= breakPrecedence)
        {
            expr = posBeforeOp;  // we backtrack before the operator (which will be handled by one of our caller)
            return leftOperand;
        }

        // reads and evaluates the expression on the right hand side
        auto rightOperand = eval(expr, prec);
        // computes the current operation, the result becoming the new left operand of the next operation
        leftOperand = computeOp(leftOperand, rightOperand, op);
    } while (true);
}

// eval function reads an expression and evaluates it (evaluates it up to the point where an operator with precedence 'breakPrecedence' is reached)
// returns the evaluation of the expression
double eval(const char*& expr, int breakPrecedence)
{
    auto leftOperand = readOperand(expr);
    return evalRight(expr, breakPrecedence, leftOperand);
}

int main()
{
    auto expression = "1 + 1 * 2 - 2 * 3 + 1";
    std::cout << "result = " << eval(expression);   // prints: result = -2
    return 0;
}

为了使代码尽可能简单,假定提供的表达式在语法上是正确的。如果需要,您可以添加一些检查。

希望这会有所帮助。

关于c++ - 无法解析不同的数学运算符,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60959198/

相关文章:

c++ - 确定数组索引到哪个数组被填充?

C++:ReadProcessMemory() 导致崩溃

c# - 审批工作流的同义词

c++ - C++编程错误

parsing - LPeg 语法怪异

function - Haskell - 尝试创建一个函数来查找奇数的阶乘

javascript - 获取嵌套数组JS中对象的所有父项

java - 是什么让 Java 比 C 更容易解析?

c++ - 在没有 for 循环的情况下解析字符串的字符数组?

haskell - 什么是异态?