Добавление унарного оператора в алгоритм маневровой станции

Я создаю программу типа калькулятора, которая переводит ввод в постфиксную нотацию, а затем вычисляет выражение. Он работает для +,-,*,/ и ^, но я не могу заставить работать унарный -. В настоящее время я просто пытаюсь заставить унарный работать в начале выражения. Я использую -5+2 для проверки алгоритма. Возвращаемый результат должен быть -3, однако программа возвращает -2. Я отметил 2 места в своем коде, которые я пытаюсь обработать унарным - подписать как

//-------------HERE IS WHERE I TRY TO HANDLE UNARY - as "u"----------------------------

Я отлаживаю весь день и не могу понять, что не работает. Вот мой код:

import java.util.ArrayList;
import java.util.Scanner;
import java.util.Stack;

public class Calculator {

    public static void main(String[] args) {
        ArrayList test = new ArrayList();
        Scanner f = new Scanner(System.in);

        System.out.println("Type and equation, then press enter: ");

        String g = f.nextLine();

        test = inputToArrayList(g);

        for (int z = 0; z < test.size(); z++) {
            System.out.println(test.get(z));
        }

        System.out.println(calculateAnswer(test));
    }

    public static class SyntaxErrorException extends Exception {

        SyntaxErrorException(String message) {
            super(message);
        }
    }
    private static final Stack<Double> operandStack = new Stack<Double>();
    private static final Stack<String> operatorStack = new Stack<String>();
    private static final String OPERATORS = "+-/*%^()u";
    private static final String NONBRACES = "+-/*%^u";
    private static final int[] PRECEDENCE = {1, 1, 2, 2, 2, 3, 3, 3, 4};

    static ArrayList<String> charList = new ArrayList<String>();

    public static ArrayList inputToArrayList(String input) {
        StringBuilder strBuild = new StringBuilder();
        String infix = input.replace(" ", "");
        try {
            for (int i = 0; i < infix.length(); i++) {
                char c = infix.charAt(i);

                boolean isNumber = (c >= '0' && c <= '9');
//------------HERE IS WHERE I HANDLE - AT BEGINNING OF INPUT, SAVED AS U INSTEAD OF -   ----------
                if (i == 0 && c == '-') {
                    isNumber = true;
                    charList.add("u");
                    continue;
                }

                if (isNumber) {
                    strBuild.append(c);
                    if (i == infix.length() - 1) {
                        charList.add(strBuild.toString());
                        strBuild.delete(0, strBuild.length());
                    }
                } else if (c == '.') {
                    for (int j = 0; j < strBuild.length(); j++) {
                        if (strBuild.charAt(j) == '.') {
                            throw new SyntaxErrorException("You can't have two decimals in a number");
                        } else if (j == strBuild.length() - 1) {
                            strBuild.append(c);
                            j = (strBuild.length() + 1);
                        }
                    }
                    if (strBuild.length() == 0) {
                        strBuild.append(c);
                    }
                    if (i == infix.length() - 1) {
                        throw new SyntaxErrorException("You can't end your equation with a decimal");
                    }
                } else if (OPERATORS.indexOf(c) != -1) {
                    if (strBuild.length() != 0) {
                        charList.add(strBuild.toString());
                        strBuild.delete(0, strBuild.length());
                    }
                    strBuild.append(c);
                    charList.add(strBuild.toString());
                    strBuild.delete(0, strBuild.length());
                } else {
                    throw new SyntaxErrorException("Make sure your input only contains numbers, operators, or parantheses");
                }
            }

            int leftParenth = 0;
            int rightParenth = 0;

            for (int p = 0; p < charList.size(); p++) {
                String checkParenth = charList.get(p);

                switch (checkParenth) {
                    case "(":
                        leftParenth++;
                        break;
                    case ")":
                        rightParenth++;
                        break;
                    default: //do nothing
                        break;
                }

            }
            if (leftParenth != rightParenth) {
                throw new SyntaxErrorException("There is not an even number of parenthesis");
            }

            int parenthesis = 0;

            for (int f = 0; f < charList.size(); f++) {
                String awesome = charList.get(f);
                switch (awesome) {
                    case "(":
                        parenthesis++;
                        break;
                    case ")":
                        parenthesis--;
                        break;
                    default:
                        break;
                }
                if (parenthesis < 0) {
                    throw new SyntaxErrorException("Order of parenthesis is off");
                }
            }
            if (NONBRACES.contains(charList.get(charList.size() - 1))) {
                throw new SyntaxErrorException("The input can't end in an operator");
            }
            return charList;
        } catch (SyntaxErrorException ex) {
            System.out.println(ex);
            return charList;
        }
    }

    private static void processOperator(String op) {
        if (operatorStack.empty() || op.equals("(")) {
            operatorStack.push(op);
        } else {
            //peek the operator stack and
            //let topOp be the top operator.
            String topOp = operatorStack.peek();
            if (precedence(op) > precedence(topOp)) {
                if (!op.equals(")")) {
                    operatorStack.push(op);
                }
            } else {
                //Pop all stacked operators with equal
                // or higher precedence than op.
                while (!operatorStack.empty() && precedence(op) >= precedence(topOp)) {
 //-------------HERE IS WHERE I TRY TO HANDLE UNARY - and "u"----------------------------                   
                    if("u".equals(operatorStack.peek())) {
                        double right = operandStack.pop();
                        String unary = operatorStack.pop();
                        operandStack.push(right * (-1));
                        break;
                    }
                    double right = operandStack.pop();
                    double left = operandStack.pop();
                    String operator = operatorStack.pop();
                    switch (operator) {
                        case "+":
                            operandStack.push(left + right);
                            break;
                        case "-":
                            operandStack.push(left - right);
                            break;
                        case "*":
                            operandStack.push(left * right);
                            break;
                        case "/":
                            operandStack.push(left / right);
                            break;
                        case "%":
                            operandStack.push(left % right);
                            break;
                        case "^":
                            operandStack.push(Math.pow(left, right));
                            break;
                        default: //do nothing, but this should never happen
                            break;
                    }

                    if (topOp.equals("(")) {
                        //matching '(' popped - exit loop.
                        operandStack.push(left);
                        operandStack.push(right);
                        break;
                    }

                    if (!operatorStack.empty()) {
                        //reset topOp
                        topOp = operatorStack.peek();
                    }
                }

                //assert: Operator stack is empty or
                // current operator precedence > top of stack operator precedence.
            }
        }
    }

    public static String calculateAnswer(ArrayList<String> infix) {
        int p;
        for (p = 0; p < infix.size(); p++) {
            if (!OPERATORS.contains(infix.get(p))) {
                double listIndex = Double.parseDouble(infix.get(p));
                operandStack.push(listIndex);
            } else {
                processOperator(infix.get(p));
            }
        }
        if (p == infix.size()) {
            while (!operatorStack.empty()) {
                if ("u".equals(operatorStack.peek())) {

                    double right = operandStack.pop();
                    String unary = operatorStack.pop();
                    operandStack.push(right * (-1));
                    break;
                }
                double right = operandStack.pop();
                double left = operandStack.pop();
                String current = operatorStack.pop();
                switch (current) {
                    case "+":
                        operandStack.push(left + right);
                        break;
                    case "-":
                        operandStack.push(left - right);
                        break;
                    case "*":
                        operandStack.push(left * right);
                        break;
                    case "/":
                        operandStack.push(left / right);
                        break;
                    case "%":
                        operandStack.push(left % right);
                        break;
                    case "^":
                        operandStack.push(Math.pow(left, right));
                        break;
                    default: //do nothing, but this should never happen
                        break;
                }
            }
        }
        return String.valueOf(operandStack.pop());
    }

    private static int precedence(String op) {
        return PRECEDENCE[OPERATORS.indexOf(op)];
    }
}

person Defqon    schedule 22.10.2020    source источник


Ответы (2)


Это не тривиальная проблема для решения. Первоначальный алгоритм Дейкстры был явно предназначен для инфиксных операторов и упоминал (но не разрешал) префиксные или постфиксные операторы. Таким образом, не существует простого расширения, универсально обрабатывающего унарные операторы.

Вам нужно будет хранить дополнительное состояние; в частности, последний тип анализируемого элемента и находитесь ли вы в «инверсном» состоянии. Если это оператор или ничего (например, начало выражения), то «-» должен переключать «инверсное» состояние. Числа должны проверять и очищать обратное состояние и при необходимости *= -1 перед добавлением в стек. Другими словами, это становится способом обработки унарного оператора как части константы, а не как оператора.

Обратите внимание, что эти два абзаца не противоречат друг другу! Приведенное выше решение не будет обрабатывать 3 * -(5 + 2), потому что оно рассматривает «-» не как оператор, а скорее как флаг, используемый при чтении чисел.

Также обратите внимание, что «правильное» решение здесь состоит в том, чтобы не использовать алгоритм маневровой станции, когда ваши операторы не все инфиксные. Используйте подходящий анализатор грамматики (например, Yacc).

person sprinter    schedule 22.10.2020

Если мы посмотрим на таблицы приоритетов языков программирования, см. Порядок операций (Википедия), мы увидим, что унарные операторы имеют уровень приоритета, отличный от соответствующего инфиксного оператора. Таблица в целом выглядит так

  1. () скобки
  2. +-! унарные операторы
  3. */ умножить и разделить
  4. +- инфиксное сложение и вычитание
  5. == операторы сравнения
  6. = операторы присваивания

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

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

Мы можем разделить наш синтаксис на две части: префиксно-суффиксные выражения, P, которые включают числа, идентифицируемые, унарные операторы (как префиксы, так и суффиксы) и выражения в квадратных скобках. Полные выражения, E, представляют собой группу P, разделенных бинарными операторами.

P :: number
   | identifier i.e. varible name
   | -P  prefix operator followed by a P
   | ( E ) an E in brackets

выражение Е будет

E :: P
  | P + E | P - E | P * E | P / E

то есть последовательность P с бинарными операторами между ними, например, P+P+PP.

Для части E вы используете маневровую станцию, но для P есть небольшая рекурсия. Его алгоритм

Eparser is
   var operators : Stack of Operator := empty
   var operands : Stack of Tree := empty
   push( operators, sentinel )
   E( operators, operands )
   expect( end )
   return top( operands )

E( operators, operands ) is
    P( operators, operands )
    while next is a binary operator
       pushOperator( binary(next), operators, operands )
       consume
       P( operators, operands )
    while top(operators) not= sentinel
       popOperator( operators, operands )

P( operators, operands ) is
    if next is a v
         push( operands, mkLeaf( v ) )
         consume
    else if next = "("
         consume
         push( operators, sentinel )   -- pushes sentinel
         E( operators, operands )      -- note recursion
         expect( ")" )                 -- remove bracket from input
         pop( operators )              -- pops sentinel
    else if next is a unary operator
         pushOperator( unary(next), operators, operands )
         consume
         P( operators, operands )
    else
         error

popOperator( operators, operands ) is
   if top(operators) is binary
        const t1 := pop( operands )
        const t0 := pop( operands )
        push( operands, mkNode( pop(operators), t0, t1 ) )
   else
        push( operands, mkNode( pop(operators), pop(operands) ) )

pushOperator( op, operators, operands ) is
    while top(operators) > op
       popOperator( operators, operands )
    push( op, operators )

Обратите внимание, что при обнаружении открытых скобок выполняется рекурсивный шаг. Сначала sentinel помещается в стек операторов, это какой-то специальный оператор, используемый для предотвращения извлечения слишком большого количества операторов. Затем код снова вызывает E. E будет работать до тех пор, пока не будет обнаружена закрывающая фигурная скобка. Когда E заканчивается, все операторы до первого часового извлекаются из стека.

person Salix alba    schedule 22.10.2020
comment
Это не очень похоже на алгоритм сортировочной станции. Это совершенно другое. Во-первых, приоритет встроен в код, а не управляется таблицей. - person user207421; 23.10.2020
comment
Приоритет бинарных операторов не встроен в код. Я не особо обращал внимание на приоритет, он появляется в третьей последней строке while top(operators) > op, которая сравнивает приоритет, и на практике выполняется с помощью таблицы поиска. Следовательно, часть E кода функционально идентична маневровой станции. - person Salix alba; 23.10.2020