分流码算法的问题

我已经在java中成功实现了一个调车码算法。 算法本身很简单但是我在使用tokenizer时遇到了麻烦。 目前,该算法适用于我想要的一切,不包括一件事。 如何区分减法( – )和否定( – )之间的区别

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

我现在知道如何找出它什么时候应该是负数,什么时候它应该是负数,但是算法应该放在哪里,因为如果你像函数一样使用它它不会总是起作用的例子

3 + 4 * 2 / – (1 – 5)^ 2 ^ 3

当1-5变为-4时,它将变为4,然后变为平方和立方

就像3 + 4 * 2 / cos(1 – 5)^ 2 ^ 3一样,你会在平方和立方之前取余弦

但是在实际数学中你不会用 – 因为你真正说的是3 + 4 * 2 / – ((1 – 5)^ 2 ^ 3)才能拥有正确的价值

听起来你正在做一个lex-then-parse风格的解析器,你需要在lexer中使用一个简单的状态机来获得单一和二进制减号的单独标记。 (在PEG解析器中,这不是你必须担心的事情。)

在JavaCC中,您将具有DEFAULT状态,您可以将-字符视为UNARY_MINUS 。 当您对主表达式的结尾(基于您给出的示例的结束表达式或整数)进行标记时,您将切换到INFIX状态,其中-将被视为INFIX_MINUS 。 一旦遇到任何中缀运算符,您将返回DEFAULT状态。

如果你自己动手,可能会比这更简单。 看看这个Python代码是否有一个聪明的方法。 基本上,当您遇到- ,您只需检查以前的令牌是否为中缀运算符。 该示例使用字符串"-u"来表示一元减号,这对于非正式标记化非常方便。 我能说的最好,Python示例确实无法处理a -跟随一个开放的paren,或者来自输入的开头的情况。 那些也应该被认为是一元的。

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

当需要进行评估时,您需要稍微改变一元运算符,因为您只需要从堆栈中弹出一个数字,而不是两个。 根据您的实现情况,可能更容易通过列表并将每个出现的"-u"替换为[-1, "*"]

如果您可以完全遵循Python,那么您应该能够在我链接的示例中看到我正在谈论的所有内容。 我发现代码比其他人提到的C版本更容易阅读。 另外,如果你很好奇,我会在Ruby中使用shunting-yard 进行一些写作,但是我将一元运算符作为一个单独的非终结符号处理,所以它们没有显示出来。

这个问题的答案可能会有所帮助。

特别是,其中一个答案引用了C中处理一元减号的解决方案 。

基本上,你必须根据二进制运算符不能存在的位置的减号的外观来识别一元减号,并为它制作不同的标记,因为它具有不同的优先级。

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

在词法分析器中,您可以实现此伪逻辑:

 if (symbol == '-') { if (previousToken is a number OR previousToken is an identifier OR previousToken is a function) { currentToken = SUBTRACT; } else { currentToken = NEGATION; } } 

您可以设置否定的优先级高于乘法和除法,但低于取幂。 您也可以将其设置为右关联(就像’^’)。 然后,您只需要将优先级和关联性集成到维基百科页面上描述的算法中。

如果令牌是运算符o1,则:在堆栈顶部有一个运算符标记o2,并且o1是左关联的,其优先级小于或等于o2的优先级,或者o1具有优先级小于o2,pop 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); } 

奥斯汀泰勒是非常正确的,你只需要为一元运算符弹出一个数字:

 if (token is operator negate) { operand = pop; push operand * -1; } 

示例项目:

https://github.com/Digipom/Calculator-for-Android/

进一步阅读:

http://en.wikipedia.org/wiki/Shunting-yard_algorithm

http://sankuru.biz/blog/1-parsing-object-oriented-expressions-with-dijkstras-shunting-yard-algorithm

我知道这是一个老post,但也许有人会发现它很有用。 之前我实现了这个算法,从使用StreamTokenizer类的toknizer开始,它工作正常。 在Java的StreamTokenizer中,有一些具有特定含义的字符。 例如:(是一个运算符,sin是一个单词,…对于你的问题,有一个名为“streamToknizer.ordinaryChar(..)”的方法,它指定该标记化器中的字符参数是“普通的”。删除角色作为注释字符,单词组件,字符串分隔符,空格或数字字符所具有的任何特殊意义。来源此处

所以你可以定义 – 作为普通字符,这意味着,它不会被视为数字的符号。例如,如果你有表达式2-3,你将有[2, – ,3],但如果你没有’ t指定为普通,所以它将是[2,-3]