在调车场算法中添加一元运算符

问题描述

我正在创建一个计算器类型的程序,该程序将输入解析为后缀表示法,然后对表达式求值。它适用于+,-,*,/和^,但我无法使一元代码正常工作。目前,我只是想让一元代码-在表达式的开头工作。我正在使用-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,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)];
    }
}

解决方法

这不是要解决的小问题。 Dijkstra的原始算法专门用于中缀运算符,并提到(但不能解决)前缀或后缀运算符。因此,没有简单的扩展可以普遍处理一元运算符。

您将需要存储其他状态;具体来说,就是解析元素的最后一种类型以及您是否处于“反向”状态。如果它是一个运算符或什么都不做(即表达式的开始),则'-'应该切换为'inverse'状态。数字应检查并清除反状态,并在必要时将*= -1添加到堆栈中。换句话说,这成为将一元运算符作为常量的一部分而不是运算符来处理的一种方式。

请注意,这两段并不矛盾!上面的解决方案不会处理3 * -(5 + 2),因为它不会将'-'视为运算符,而是将其作为读取数字时使用的标志。

还请注意,此处的“适当”解决方案是,当您的操作员并非全都采用中缀法时,不要使用调车场算法。使用适当的语法分析器(例如Yacc)。

,

如果我们查看编程语言的优先级表,请参见Order of Operations (Wikipedia),我们看到一元运算符的优先级与相应的infix运算符不同。该表通常看起来像

  1. ()括号
  2. +-! 一元运算符
  3. * / 乘除除
  4. +-中缀加减法
  5. == 比较运算符
  6. = 赋值运算符

所以您的程序需要一种区分具有相同符号的一元和二进制运算符的方法。在Dijkstra的著作中,他给一元负号与*和/的优先级相同,但没有说明解析器如何区分一元和二进制情况。

我已经成功地遵循了Parsing Expressions by Recursive Descent中的技术,该技术与调车场算法非常相似,但是有一些递归来处理括号。

我们可以将语法分为两部分:前缀后缀表达式P(包括数字,已标识的一元运算符(既作为前缀又作为后缀))和方括号表达式。完整的表达式E是一堆由二进制运算符分隔的P。

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

表达式E将为

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

即P的序列,它们之间有二元运算符,即P + P + P-P。

对于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),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,pop(operands) ) )

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

请注意,遇到方括号时有一个递归步骤。首先将 sentinel 压入运算符堆栈,这是一些特殊的运算符,用于防止弹出过多的运算符。该代码然后再次调用E。 E将运行直到发现右括号为止。当E结束时,从堆栈中弹出直到第一个前哨的所有运算符。