Java 调车场算法的问题

Java 调车场算法的问题,java,subtraction,shunting-yard,Java,Subtraction,Shunting Yard,我已经成功地用java实现了一个调车场算法。算法本身很简单,但是我在标记器方面遇到了问题。目前,该算法适用于我想要的任何东西,不包括一件事。如何区分减法(-)和负(-)之间的区别 例如4-3是减法 但是-4+3是负的 我现在知道如何找出它什么时候应该是负数,什么时候应该是负数,但是它应该放在算法中的什么地方,因为如果你把它当作一个函数来使用,它就不会一直起作用 3+4*2/-(1− 5)^2^3 当1-5变成-4时,在它被平方和立方之前,它将变成4 就像 3+4*2/cos(1)− 5)^2^3

我已经成功地用java实现了一个调车场算法。算法本身很简单,但是我在标记器方面遇到了问题。目前,该算法适用于我想要的任何东西,不包括一件事。如何区分减法(-)和负(-)之间的区别

例如4-3是减法 但是-4+3是负的

我现在知道如何找出它什么时候应该是负数,什么时候应该是负数,但是它应该放在算法中的什么地方,因为如果你把它当作一个函数来使用,它就不会一直起作用

3+4*2/-(1− 5)^2^3

当1-5变成-4时,在它被平方和立方之前,它将变成4

就像 3+4*2/cos(1)− 5)^2^3,在进行平方和立方运算之前取余弦

但在真正的数学中,你不会用a,因为你真正说的是 3 + 4 * 2 / -(( 1 − 5)^2^3)为了获得正确的值,

的答案可能会有所帮助

特别是,其中一个答案引用了C中处理一元负号的a

基本上,你必须根据减号在二元运算符不能出现的位置上的出现来识别一元负号,并为它做一个不同的标记,因为它具有不同的优先级


Dijkstra没有太清楚地解释他是如何处理这个问题的,但是一元负号被列为一个单独的运算符。

听起来你在做一个lex-then-parse风格的解析器,在这个解析器中,你需要一个简单的状态机,以便为一元负号和二元负号获得单独的标记。(在PEG解析器中,这不需要担心。)

在JavaCC中,您将有一个<代码>默认状态,在这里您可以考虑<代码> -/COD>字符为<代码> UNARYAY-UMUS。当您标记主表达式的结尾时(根据您给出的示例,可以是一个闭包,也可以是一个整数),那么您将切换到

中缀
状态,其中
-
将被视为
中缀
。一旦遇到任何中缀运算符,您将返回到默认状态

如果你自己滚动,可能会简单一点。看看这是一个聪明的方法。基本上,当您遇到
-
时,您只需检查前面的令牌是否是中缀运算符。该示例使用字符串
“-u”
表示一元减号标记,这便于非正式标记化。据我所知,Python示例确实无法处理以下情况:
-
紧跟在打开的paren之后,或者出现在输入的开头。这些也应该被认为是一元的

为了在调车场算法中正确处理一元负号,它需要比任何中缀运算符具有更高的优先级,并且需要标记为右关联。(请确保处理右关联性。由于其余运算符都是左关联的,因此可能将其忽略。)这在Python代码中已经足够清楚了(尽管我会使用某种结构,而不是两个单独的映射)

在计算时,您需要稍微不同地处理一元运算符,因为您只需要从堆栈中弹出一个数字,而不是两个。根据实现的外观,只需浏览列表并将出现的
“-u”
替换为
[-1,“*”]
,可能会更容易


如果您能够完全理解Python,那么您应该能够看到我在链接的示例中所讨论的所有内容。我发现代码比其他人提到的C版本更容易阅读。另外,如果你好奇的话,我之前写过一篇关于使用调车场的文章,但是我把一元运算符作为一个单独的非终结符来处理,所以它们没有显示出来。

在你的lexer中,你可以实现这个伪逻辑:

if (symbol == '-') {
    if (previousToken is a number 
     OR previousToken is an identifier 
     OR previousToken is a function) {
        currentToken = SUBTRACT;
    } else {
        currentToken = NEGATION;
    }
}
可以将求反设置为优先级高于乘除,但低于乘幂。您还可以将其设置为右关联(就像“^”)。 然后您只需要将优先级和关联性集成到算法中,如Wikipedia页面所述

如果令牌是运算符o1,则:while is a operator 标记o2位于堆栈顶部,o1中的任何一个都是左关联的 其优先级小于或等于o2,或o1具有 优先级低于o2,将o2从堆栈中弹出到输出上 队列将o1推到堆栈上

我最终实现了相应的代码:

} else if (nextToken instanceof Operator) {
    final Operator o1 = (Operator) nextToken;

    while (!stack.isEmpty() && stack.peek() instanceof Operator) {
        final Operator o2 = (Operator) stack.peek();

        if ((o1.associativity == Associativity.LEFT && o1.precedence <= o2.precedence)
         || (o1.associativity == Associativity.RIGHT && o1.precedence < o2.precedence)) {
            popStackTopToOutput();
        } else {
            break;
        }
    }

    stack.push(nextToken);
}
示例项目:

进一步阅读:


我知道这是一篇老文章,但可能有人会发现它很有用。 我以前实现过这个算法,从使用StreamTokenizer类的toknizer开始 而且效果很好。在Java中的StreamTokenizer中,有一些具有特定含义的字符。例如:(是运算符,sin是单词,。。。 对于您的问题,有一个名为“streamToknizer.ordinaryChar(…)”的方法,它指定字符参数在此标记器中为“普通”。它删除字符作为注释字符、单词组件、字符串分隔符、空白或数字字符的任何特殊意义。源


因此,您可以将-定义为普通字符,这意味着它不会被视为数字的符号。例如,如果您有表达式2-3,您将有[2,-,3],但如果您没有将其指定为普通字符,那么它将是[2,-3]

我添加了“java”标签,我想它可能会让你的问题获得更多的视图。标准调车场算法不支持它们,我试图修改它以支持它们。Wolfram alpha、texas instruments、Wolfram mathematica、microsoft math等。尽管支持它们,但所有这些都使用调车场算法的版本。这看起来很棒,但是一元负号应该比任何其他运算符具有更高的优先级
if (token is operator negate) {
    operand = pop;
    push operand * -1;
}