Неспособность разобрать разные математические операторы - PullRequest
3 голосов
/ 31 марта 2020

Этот вопрос является продолжением от этого . В основном я пытаюсь сделать парсер, который вычисляет общий результат строки. 5+5+3*2/1 должно дать 16. Это уже работает для строк, содержащих только плюсы и минусы, поэтому -55-44+1-2+123-54442+327737+1-2 успешно дает 273317.

Однако это не работает, когда плюсы / мин смешиваются со временем / делением. Таким образом, 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; dr Этот код отлично работает для строк, содержащих только плюсы и минусы или только раз и делит. Сочетание времен и делений портит это. Возможно, параметр оператора не обновляется. Как это исправить?

Ответы [ 6 ]

3 голосов
/ 06 апреля 2020

Извините, если я не проанализировал ваш код подробно, потому что он слишком сложен для того, что вы пытаетесь сделать. Поэтому я не скажу вам, в чем именно проблема, вместо этого я предложу вам что-то более простое.

Так или иначе вам нужно управлять стеком, потому что выражение алгебры c должно обрабатываться как дерево структура и процесс оценки должны следовать этой структуре. Это не может быть обработано как плоская структура, и вы не можете избежать управления приоритетом оператора. В дополнение к этому выражение обычно оценивается слева направо (ассоциативность слева).

При этом, если вы действительно не хотите использовать инструмент синтаксического анализа (который ИМХО был бы более простым и чистым), он всегда можно разобрать "вручную". В этом случае вы можете избежать управления явным стеком, используя сам стек вызовов, как показано в следующем коде:

#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;
}

Чтобы сделать код максимально простым, предоставляемое выражение предполагается синтаксически правильным , Вы можете добавить некоторые проверки, если хотите.

Надеюсь, это поможет.

2 голосов
/ 03 апреля 2020

Как вы сказали

Я хотел бы создать что-то свое, это не рабочий код. Просто хобби.

так что, вероятно, вы хотите узнать одну или две вещи. Вот почему я не буду писать здесь никакого кода и украсть у вас все самое интересное.

Похоже, вы должны начать с основ. Я мог бы порекомендовать вам Книгу Дракона , но вы, вероятно, захотите испачкать руки прямо сейчас, вместо того, чтобы читать классику в течение недели. Итак, вы можете начать с PEGs - это действительно просто. Я начал любить разбор после того, как прочитал эту статью .

В вашем случае грамматика будет довольно простой:

Expr    ← Sum
Sum     ← Product (('+' / '-') Product)*
Product ← Value (('*' / '/') Value)*
Value   ← [0-9]+

С помощью функций вы можете переписать это так:

value   = repeat_at_least_once(character("0"),...,character("9"))
product = sequence(value  , repeat(one_of(character("*"),character("/")), value  ) 
expr    = sequence(product, repeat(one_of(character("+"),character("-")), product)

Все, что вам нужно сделать сейчас - написать эти функции :) Это будет не намного дольше, чем код, который вы написали, если не короче.

Если вы заполняете уверенно, вы даже можете реализовать синтаксический анализ пакетов с левой поддержкой рекурсии, в этом случае ваша грамматика будет еще проще.

1 голос
/ 09 апреля 2020

вам нужно разделить вычисления на несколько шагов

  1. скопировать выражение в доступную для записи память и проверить / нормализовать его:

. Проверить, что все символы действительны (положительные)

. Удалить пробелы

. Преобразовать все в нижний (или верхний) регистр (если вы используете шестнадцатеричные выражения)

. Некоторые операторы принимают 2 символа ( == , ! = , > = , <= </em>, << </em>, >> , || , && ) - заменить его на одиночный символ из недопустимого (отрицательного) диапазона

удалить () , если существует - вычислить выражения в () :

. Найти сначала ) символ с начала

. Найти последний ( перед ним.

. Проверить, что после ) и до ( были символы-разделители (оператор или начало / конец строки), но не di git.

.формат новой строки, где вы заменяете (..) на цифровой результат

удалить (вычислить) все унарные операторы ( +, -,!, ~ )

. Унарные операторы - справа должны иметь di git, а слева - другой оператор (или начало строки), но не di git

.формат новой строки с результатом унарного оператора

удалить (вычислить) все двоичные операторы.

. Нам нужно вычислить в обратном порядке приоритета - поэтому сначала нужно вычислить / удалить операторы с самым низким приоритетом.

.so нужно сделать l oop операторами (от низкого до высокого приоритета) - символ оператора поиска в строке.

.Если найдено - A op B - вычислить отдельно A и B , а затем применить op .

преобразовать строку в целое число

. Теперь, после того как все () и операторы удалены - только строка * git должна быть в строке

пример кода:

namespace Eval 
{
    typedef INT_PTR (* fn_b_op)(INT_PTR a, INT_PTR b);
    typedef INT_PTR (* fn_u_op)(INT_PTR a);

    struct b_op_arr { fn_b_op pfn; char c; };
    struct u_op_arr { fn_u_op pfn; char c; };

    struct name_to_char { char b[3]; char c;};

    static INT_PTR fn1_bnt(INT_PTR a){ return !a; }
    static INT_PTR fn1_not(INT_PTR a){ return ~a; }
    static INT_PTR fn1_add(INT_PTR a){ return +a; }
    static INT_PTR fn1_sub(INT_PTR a){ return -a; }

    static INT_PTR fn2Land(INT_PTR a,INT_PTR b){ return a && b; }
    static INT_PTR fn2_Lor(INT_PTR a,INT_PTR b){ return a || b; }
    static INT_PTR fn2_equ(INT_PTR a,INT_PTR b){ return a == b; }
    static INT_PTR fn2_nqu(INT_PTR a,INT_PTR b){ return a != b; }
    static INT_PTR fn2_lqu(INT_PTR a,INT_PTR b){ return a < b;  }
    static INT_PTR fn2_gqu(INT_PTR a,INT_PTR b){ return a > b;  }
    static INT_PTR fn2_leu(INT_PTR a,INT_PTR b){ return a <= b; }
    static INT_PTR fn2_geu(INT_PTR a,INT_PTR b){ return a >= b; }
    static INT_PTR fn2_add(INT_PTR a,INT_PTR b){ return a + b;  }
    static INT_PTR fn2_sub(INT_PTR a,INT_PTR b){ return a - b;  }
    static INT_PTR fn2_mul(INT_PTR a,INT_PTR b){ return a * b;  }
    static INT_PTR fn2_div(INT_PTR a,INT_PTR b){ return a / b;  }
    static INT_PTR fn2_dv2(INT_PTR a,INT_PTR b){ return a % b;  }
    static INT_PTR fn2_lsh(INT_PTR a,INT_PTR b){ return (UINT_PTR)a << b; }
    static INT_PTR fn2_rsh(INT_PTR a,INT_PTR b){ return (UINT_PTR)a >> b; }
    static INT_PTR fn2_xor(INT_PTR a,INT_PTR b){ return a ^ b; }
    static INT_PTR fn2_and(INT_PTR a,INT_PTR b){ return a & b; }
    static INT_PTR fn2__or(INT_PTR a,INT_PTR b){ return a | b; }

    enum /*: char*/ { equ = -0x80, not_equ, less_equ, gre_equ, l_or, l_and, r_shift, l_shift };

    inline static b_op_arr b_arr[] = 
    {
        {fn2_mul, '*'}, {fn2_div, '/'}, {fn2_lsh, l_shift}, {fn2_rsh, r_shift},
        {fn2_xor, '^'}, {fn2_dv2, '%'}, {fn2_and, '&'}, {fn2__or, '|'},
        {fn2_equ, equ}, {fn2_nqu, not_equ}, {fn2_lqu, '<'}, {fn2_gqu, '>'},
        {fn2_leu, less_equ},{fn2_geu, gre_equ},{fn2_add, '+'}, {fn2_sub, '-'},
        {fn2Land, l_and}, {fn2_Lor, l_or}
    };

    inline static u_op_arr u_arr[] = 
    {
        {fn1_add, '+'}, {fn1_sub, '-'}, {fn1_bnt,'!'}, {fn1_not,'~'}
    };

    inline static name_to_char _2_to_1[] =
    {
        {"==", equ}, {"!=", not_equ}, {"<=", less_equ}, {">=", gre_equ }, 
        {">>", r_shift}, {"<<", l_shift}, {"||", l_or}, {"&&", l_and}, 
    };

    void initBits(LONG bits[], const char cc[], ULONG n)
    {
        do 
        {
            _bittestandset(bits, cc[--n]);
        } while (n);
    }

    static bool IsSeparatorSymbol(char c)
    {
        static LONG bits[8];
        static bool bInit;

        if (!bInit)
        {
            // acquire
            static const char cc[] = { 
                '*', '/', '+', '-', '^', '%', '&', '|', '<', '>', '!', '~', '(', ')',
                equ, not_equ, less_equ, gre_equ, l_or, l_and, r_shift, l_shift, 0
            };

            initBits(bits, cc, _countof(cc));

            // release
            bInit = true;
        }

        return _bittest(bits, c);
    }

    static bool IsUnaryOpSymbol(char c)
    {
        static LONG bits[8];
        static bool bInit;

        if (!bInit)
        {
            // acquire
            static char cc[] = { 
                '+', '-', '!', '~'
            };

            initBits(bits, cc, _countof(cc));

            // release
            bInit = true;
        }

        return _bittest(bits, c);
    }

    static bool IsDigit(char c)
    {
        static LONG bits[8];
        static bool bInit;

        if (!bInit)
        {
            // acquire
            static char cc[] = { 
                '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
            };

            initBits(bits, cc, _countof(cc));

            // release
            bInit = true;
        }

        return _bittest(bits, c);
    }

    __int64 strtol64_16(char* sz, char** psz)
    {
        __int64 r = 0;

        while (char c = *sz)
        {
            ULONG d;

            if ((ULONG)(c - '0') <= '9' - '0')
            {
                d = (c - '0');
            }
            else if ((ULONG)(c - 'a') <= 'z' - 'a')
            {
                d = (c - 'a') + 10;
            }
            else
            {
                break;
            }

            r = (r << 4) + d;
            sz++;
        }

        *psz = sz;

        return r;
    }

    bool Normalize(const char* psz, char* buf, size_t s)
    {
        int len = 0;

        do 
        {
            --s;
            char c = *psz++, d;

            // is valid char
            if (c < 0) return false;

            // skip space
            if (c == ' ') continue;

            if ((ULONG)(c - 'A') < (ULONG)('Z' - 'A'))
            {
                c += 'a' - 'A';
            }

            // not last char
            if (s)
            {
                d = *psz;

                int k = _countof(_2_to_1);
                do 
                {
                    if (_2_to_1[--k].b[0] == c && _2_to_1[k].b[1] == d)
                    {
                        c = _2_to_1[k].c, psz++, --s;
                        break;
                    }
                } while (k);
            }

            *buf++ = c, len++;

        } while (s);

        return 0 < len;
    }

    char* format_new_str(const char* a, INT_PTR r, const char* b)
    {
        static const char format[] = "%s%I64x%s";

        int len = _scprintf(format, a, r, b);

        if (0 < len)
        {
            if (char* buf = new char [++len])
            {
                if (0 < sprintf_s(buf, len, format, a, r, b))
                {
                    DbgPrint("++%p\n\"%s\"\n", buf, buf);
                    return buf;
                }

                delete buf;
            }
        }

        return 0;
    }

    bool _calc (char* str, INT_PTR& result)
    {
        DbgPrint("\"%s\"\n", str);
        struct SB 
        {
            char* str;

            SB() : str(0) {}

            ~SB()
            {
                operator <<(0);
            }

            void operator <<(char* psz)
            {
                if (str) 
                {
                    DbgPrint("--%p\n", str);
                    delete [] str;
                }
                str = psz;
            }
        } sb;

        size_t len = strlen(str);

        if (!len)
        {
            return false;
        }

        char b, c;
        int l;
        INT_PTR r, q;

        //1. remove ( )
        char *psz = str, *pc = 0, *buf;

        for (;;)
        {
            switch (*psz++)
            {
            case '(':
                pc = psz;
                continue;

            case ')':

                if (!pc || !IsSeparatorSymbol(*psz) || (pc > str + 1 && !IsSeparatorSymbol(pc[-2]))) return false;

                psz[-1] = 0, pc[-1] = 0;

                if (_calc(pc, r) && (buf = format_new_str(str, r, psz)))
                {
                    sb << buf;
                    psz = str = buf, pc = 0;
                    continue;
                }

                return false;
            case 0:
                goto __2;
            }
        }

__2:
        //2. remove unary op

        psz = str;

        do 
        {
            if (IsDigit(c = *psz) && str < psz && IsUnaryOpSymbol(c = psz[-1]) && (psz == str + 1 || IsSeparatorSymbol(psz[-2])))
            {
                psz[-1] = 0;
                l = _countof(u_arr);
                do 
                {

                    if (u_arr[--l].c == c)
                    {
                        r = strtol64_16(psz, &psz);
                        if (IsSeparatorSymbol(*psz))
                        {
                            r = u_arr[l].pfn(r);

                            if (buf = format_new_str(str, r, psz))
                            {
                                sb << buf;
                                psz = str = buf;
                                goto __2;
                            }
                        }
                        break;
                    }
                } while (l);

                return false;
            }
        } while (psz++, c);

        //3. remove binary op

        l = _countof(b_arr);
        do 
        {
            c = b_arr[--l].c;

            psz = str;

            do 
            {
                if (c == (b = *psz++))
                {
                    psz[-1] = 0;
                    if (_calc(psz, q) && _calc(str, r))
                    {
                        result = b_arr[l].pfn(r, q);

                        return true;
                    }

                    return false;
                }
            } while (b);

        } while (l);

        result = strtol64_16(str, &str);

        return !*str;
    }

    bool calc(const char* psz, INT_PTR& result)
    {
        bool fOk = false;

        if (size_t s = strlen(psz))
        {
            if (char* buf = new char[++s])
            {
                if (Normalize(psz, buf, s))
                {
                    fOk = _calc(buf, result);
                }

                delete [] buf;
            }
        }

        return fOk;
    }
};

использование

INT_PTR r;
Eval::calc(str, r);
1 голос
/ 09 апреля 2020

Хотя я четко заявил, что не хочу использовать постфиксное решение, на самом деле я понял, что это наиболее разумное решение. Я сделал постфиксное решение самостоятельно с помощью обучающих программ (и все еще многому научился!). Спасибо всем за помощь и предложения.

#include <iostream>
#include <string>
#include <stack>

/*
 * Function to check if a given character is an operator (+, -, *, /) or not
 * :param c: Character to check
 * :return bool: Returns true if parameter c is an operator
 */ 
bool isOperator(char c) {
    char operators[4] = {'+', '-', '*', '/'};
    if (std::find(std::begin(operators), std::end(operators), c) != std::end(operators)) {
        return true;
    }

    return false;
}

/*
 * Function to get the precedence matching the character
 *
 * :param a: Character containing the operator to get precedence from
 * :return int: Integer representing precedence. Operators with high precedence (e.g * and /) return a higher value than e.g + and -.
 *
 * Example:
 * precedence('*') > precedence('+') == true
 *
 */
int precedence(char a) {
    switch (a) {
        case '+': return 1;
                  break;
        case '-': return 1;
                  break;
        case '*': return 2;
                  break;
        case '/': return 2;
                  break;
    }

    return -1;
}

/*
 * Function to convert an infix string to postfix notation
 * :param infix: Infix string
 * :return string: returns postfix string
 *
 * Example:
 * std::string s = "5+5";
 * toPostfix(s) == "5 5 +"
 *
 */
std::string toPostfix(std::string& infix) {
    std::string postfix = "";

    //Stack to hold operators and nr is a helper string to
    //group digits in numbers
    std::stack<char> stack;
    std::string nr = "";

    //If first character is a minus-operator (AKA a negative number)
    //add "0"
    if (infix[0] == '-') {
        infix = "0" + infix;
    }

    //Looping over infix string
    for (int i = 0; i < infix.size(); i++) {
        //If current evaluated character ain't an operator, it's a digit
        if (!isOperator(infix[i])) {
            //If digit is in a group of digits (AKA a number) put the whole number in nr
            while (!isOperator(infix[i]) && i < infix.size()) {
                nr += infix[i];
                i++;
            }

            i--;

            //Append the number to the postfix string
            postfix += nr + " ";
            nr = "";
        } else {
            //This block is executed when evaluated character is an operator

            //If stack is empty, or the evaluated operator is higher than the one in the stack
            //push it to the stack (Needs to be appended to the postfix string later)
            if (stack.size() == 0 || precedence(infix[i]) > precedence(stack.top())) {
                stack.push(infix[i]);
            } else {
                //While the stack contacts a higher or equally high precedence as currently
                //evaluated operator
                while (precedence(stack.top()) >= precedence(infix[i])) {
                    //We append the top of the stack to the postfix string
                    postfix += stack.top();
                    postfix += ' ';

                    stack.pop();
                    if (stack.size() == 0) {
                        break;
                    }
                }

                //Push evaluated operator to stack
                stack.push(infix[i]);
            }
        }
    }

    //Append all remaining operators to the postfix string
    while (stack.size() != 0) {
        postfix += stack.top();
        stack.pop();
    }

    return postfix;
}

/*
 * Evaluate two numbers regaring the used operator
 * :param x: First number to do evaluation with
 * :param y: Second number to do evaluation with
 * :param _operator: Operator to do calculation with
 * :return double: Result of the evaluation
 *
 * Example:
 *  x: 5
 *  y: 60
 *  _operator: +
 *  = 65
 */
double evaluate(double x, double y, char _operator) {
    switch(_operator) {
        case '+':
            return x + y;
            break;
        case '-':
            return x - y;
            break;
        case '*':
            return x * y;
            break;
        case '/':
            return x / y;
            break;
    }

    return 0;
}

/*
 * Calculate the result of an infix string
 * :param s: String containing the infix notation
 * :return double: Result of the calculation
 *
 * Example:
 * std::string s = "5+5";
 * calculate(s) == 10
 */
double calculate(std::string& s) {

    //Convert infix to postfix
    s = toPostfix(s);

    //Stack holding operators and nr (string) for separating numbers
    std::stack<double> stack;
    std::string nr = "";

    //Looping over postfix string
    for (int i = 0; i < s.size(); i++) {
        if (s[i] == ' ') {
            continue;
        }

        //If evaluated character is a digit,
        //put it in nr
        if (isdigit(s[i])) {
            //If digit is first of a group of digits, put that group of digits
            //AKA a number in nr
            while (isdigit(s[i])) {
                nr += s[i];
                i++;
            }
            i--;

            //Pushing nr in stack
            stack.push(std::stod(nr));
            nr = "";
        } else {
            //If current evaluated character is not a digit
            //but an operator, do a calculation

            //Retrieve first number for calculation
            int x = stack.top();
            stack.pop();

            //Retrieve second number for calculation
            int y = stack.top();
            stack.pop();

            //Put evaluation result in integer and push into stack
            int result = evaluate(y, x, s[i]);
            stack.push(result);
        }
    }

    //Final number is in stack
    return stack.top();
}



int main() {

    std::string s = "-5*5-2*2+3-10/5";
    std::cout << calculate(s) << std::endl;


}


1 голос
/ 05 апреля 2020

ИМХО, ваш текущий подход (сначала делайте умножения и деления, затем продолжая сложение и вычитание и все в одной функции), в лучшем случае будет болезненным. Ваша функция calculate уже очень трудно рассуждать, потому что она уже смешивает несколько случаев, например,

  • первый проход или второй проход (в зависимости от содержимого строки action, которая является текущей статус выражения, которое вы изменяете от звонка к звонку)
  • first_nr пусто / заполнено
  • second_nr пусто / заполнено

Теперь представьте, что больше добавлены операторы, такие как ^ и ( и ). Я понимаю, что это хобби-проект. Но даже если вы заставите это сработать однажды, вы не сможете понять это через неделю.

Поскольку вы хотите повторно использовать текущий код, как об этом:

Подумайте, как бы вы (как человек) об этом go? Есть несколько подходов. Независимо от указанного c алгоритма они состоят из двух частей:

  • Токенизация (идентификационные номера и операторы)
  • Оценка (объединение этих номеров и операторов)

Вы смешиваете обе части в своем коде. Было бы намного проще для вас и всех, кого вы просите о помощи, разделить их.

Токенизация проста (вы уже это делаете, хотя я бы рекомендовал рассматривать строку выражения только для чтения).

Оценка более сложна, потому что вы должны подумать о приоритете оператора. Но опять же, это помогает подумать о том, как бы вы сделали это как человек. Вы можете читать слева направо. Как вы справляетесь с этим как личность? Сначала вы можете оценить подвыражения с более высоким приоритетом (как вы собираетесь делать сейчас). Как вы храните токены? Подумайте о разных структурах данных. Списки, стеки или очереди для примеров.

Есть много способов. Как только вы нашли его, поискать литературу должно быть весело.

Наслаждайтесь!

0 голосов
/ 10 апреля 2020

Во время чтения Обучение Go Я реализовал некоторые из предложенных программ обучения. Одно из них имеет почти те же требования, что и вы, хотя я должен признать, что ваши требования более развиты. Итак, я надеюсь, что вы можете извлечь что-то из этого кода (я знаю, что это не C ++, но я уверен, что вы можете прочитать его):

package main

import (
  "fmt"
  "os"
  "bufio"
  "stack"
  "strconv"
)

func readInput() string {
  reader := bufio.NewReader(os.Stdin)
  switch in, ok := reader.ReadString('\n'); true {
  case ok != nil:
    fmt.Printf("Failed to read inputs: %v", ok)
    return "error"
  default:
    return in[:len(in)-1]
  }
}

func isdigit(in string) bool {
  _,ok := strconv.Atoi(in)
  return ok == nil
}

func isOperation(in string) bool {
  chars := []rune(in)
  return '+' == chars[0] || '-' == chars[0] || '*' == chars[0] || '/' == chars[0]
}

func calc(operation string, op2, op1 int) float32 {
  chars := []rune(operation)
  switch chars[0] {
  case '+':
    return float32(op1 + op2)
  case '-':
    return float32(op1 - op2)
  case '*':
    return float32(op1 * op2)
  case '/':
    return float32(op1) / float32(op2)
  }
  print("Failed to recognize operation: ")
  println(operation)
  fmt.Printf("%v\n", chars)
  return 0.0
}

func main() {
  var st stack.Stack

  fmt.Println("Calculator.")
  fmt.Println("Please input operations and then one of + - * / for calculation,")
  fmt.Println("or anything else for exit.")

LOOP:  for {
    in := readInput()
    switch {
    case isdigit(in):
      i,_ := strconv.Atoi(in)
      st.Push(i)
    case isOperation(in):
      op2 := st.Pop()
      op1 := st.Pop()
      res := calc(in, op2, op1)
      st.Push(int(res))
      fmt.Println(res)
    default:
      fmt.Println("Exit")
      break LOOP
    }
  }
}

... похоже, не так ли?

...