【问题标题】:Parsing an arithmetic expression and building a tree from it in Java在 Java 中解析算术表达式并从中构建树
【发布时间】:2011-01-04 01:31:43
【问题描述】:

在给定算术表达式的情况下,我需要一些帮助来创建自定义树。比如说,你输入这个算术表达式:

(5+2)*7

结果树应如下所示:

    *
   / \
  +   7
 / \
5   2

我有一些自定义类来表示不同类型的节点,即 PlusOp、LeafInt 等。我不需要评估表达式,只需创建树,这样我以后就可以对其执行其他功能。 另外,否定运算符“-”只能有一个子,要表示“5-2”,必须输入为5 + (-2)。

需要对表达式进行一些验证,以确保每种类型的运算符都有正确的编号。对于参数/子项,每个左括号都伴随着一个右括号。

另外,我应该提一下我的朋友已经编写了将输入字符串转换为令牌堆栈的代码,如果这对此有所帮助的话。

如果有任何帮助,我将不胜感激。谢谢:)

(我读到您可以编写语法并使用 antlr/JavaCC 等来创建解析树,但我不熟悉这些工具或编写语法,所以如果这是您的解决方案,我会如果您能为他们提供一些有用的教程/链接,将不胜感激。)

【问题讨论】:

  • 我应该补充一点,我也会对逻辑表达式(例如 ¬A V B)做类似的事情。
  • 查看我关于如何构建递归下降解析器的 SO 答案,这对于表达式来说真的很容易。该答案链接到第二个,它显示了如何使用这样的解析器构建树。 stackoverflow.com/questions/2245962/…

标签: java parsing tree


【解决方案1】:

假设这是某种家庭作业,而您想自己做......

我做过一次,你需要一个堆栈

所以你为这个例子做的是:

解析做什么?堆栈看起来像 (将其推入堆栈( 5 推 5 (, 5 + 推 + (, 5, + 2 按 2 (, 5, +, 2 ) 评估直到 ( 7 * 推 * 7, * 7 推 7 +7, *, 7 eof 评估直到前 49

“5”或“+”之类的符号可以只存储为字符串或简单对象,或者您可以将 + 存储为 +() 对象而不设置值并在评估时设置它们。

我认为这也需要优先顺序,所以我将描述它是如何工作的。

如果是:5 + 2 * 7

你必须推 5 推 + 推 2 下一个操作的优先级更高,所以你也推它,然后推 7。当你遇到 a ) 或文件结尾或优先级较低或相等的运算符时,你开始计算堆栈到前一个(或文件的开头。

因为您的堆栈现在包含 5 + 2 * 7,所以当您评估它时,您首先弹出 2 * 7 并将生成的 *(2,7) 节点推入堆栈,然后再次评估前三项堆栈(5 + *节点)所以树出来是正确的。

如果它以另一种方式排序:5 * 2 + 7,您将推送直到您到达具有“5 * 2”的堆栈,然后您将达到较低的优先级 +,这意味着评估您现在所拥有的。您将 5 * 2 评估为 *node 并将其推送,然后您将继续推送 + 和 3,这样您就有了 *node + 7,此时您将评估它。

这意味着您有一个“最高当前优先级”变量,当您按下 +/- 时存储一个 1,当您按下 * 或 / 时存储一个 2,以及一个用于“^”的 3。这样,您只需测试变量以查看下一个运算符的优先级是否为

如果 ")" 被认为是优先级 4,您可以将其视为其他运算符,但它会删除匹配的 "(",较低的优先级不会。

【讨论】:

  • 不就是 Edsger Dijkstra 的“Shunting Yard”算法吗? (en.wikipedia.org/wiki/Shunting-yard_algorithm)
  • @SasQ 最好尝试解释一些东西而不是传递一个链接——教你和他们。另外,我从来没有把它看作是一种算法,有人向我提到可以在树中进行计算,我做到了——不知道要查找的名称(虽然我知道它已经重复做过,但并不复杂--这就是我回答的原因)
  • @Bill K:我不会贬低你解释的努力。很好。如果有人想查看更多信息,我只粘贴一个链接作为参考。是的,解释比传链接好,但如果链接的文章中已经解释过了,最好传链接,节省时间,而不是重新发明轮子。网络上重复的相同知识的副本太多了。
  • @Bill K:“当您遇到 a ) 或文件结尾或优先级较低或相等的运算符时,您开始计算堆栈......” 1 + 3 * 2 * 4 是什么?当我遇到第二个“*”时,我必须计算整个 stck,即 1+3*2,即 7。这会导致错误答案 (7 * 4 =) 28。而正确答案是 25 (= 1 + 24)。
  • @ArtemPelenitsyn * 高于 +,因此当您在堆栈中点击 + 时,您必须停止评估,而不是执行整个堆栈。我认为您是对的,应该明确指出,如果您在堆栈中遇到较低优先级的运算符,您就会停止
【解决方案2】:

我想回复 Bill K. 的回答,但我缺乏在那里添加评论的声誉(这确实是这个答案的归属)。您可以将其视为 Bill K. 答案的附录,因为他的答案有点不完整。缺少的考虑是operator associativity;即,如何解析如下表达式:

49 / 7 / 7

根据除法是左结合还是右结合,答案是:

49 / (7 / 7) => 49 / 1 => 49

(49 / 7) / 7 => 7 / 7 => 1

通常,除法和减法被认为是左结合的(即上面的情况二),而取幂是右结合的。因此,当您遇到一系列具有相同优先级的运算符时,如果它们是左关联的,您希望按顺序解析它们,如果它们是右关联的,则按相反的顺序解析它们。这只是确定您是推入堆栈还是弹出堆栈,因此它不会使给定算法过于复杂,它只是增加连续运算符具有相同优先级的情况(即,如果左关联则评估堆栈,如果右关联则推入堆栈) .

【讨论】:

  • 您不应添加 cmets 作为答案。尝试使其成为一个独立的答案或获得一些代表并添加评论。
【解决方案3】:

"Five minute introduction to ANTLR" 包含一个算术语法示例。值得一试,特别是因为 antlr 是开源的(BSD 许可)。

【讨论】:

  • 感谢您的链接。虽然目前我会尝试 Bill 发布的堆栈解决方案,因为我的一部分想自己做这一切,但如果我回到 ANTLR,我肯定会使用这个链接,看起来成为对新手最有帮助的介绍:)
  • @CameronSkinner,需要注意的是 ANTLR 不能被认为是开源项目,因为开源项目也应该开源文档,(这是项目的一部分),但是ANTLR 的文档不是免费的,所以它是一个免费软件,但不是开源的from wikipedia
【解决方案4】:

为您提供多种选择:

  1. 重用现有的表达式解析器。如果您在语法和语义上灵活,那将起作用。我推荐的一种好方法是内置于 Java 中的统一表达式语言(最初用于 JSP 和 JSF 文件)。

  2. 从头开始编写您自己的解析器。有一种定义明确的方法可以编写考虑运算符优先级等的解析器。准确描述如何完成不在此答案的范围内。如果你走这条路,给自己找一本关于编译器设计的好书。语言解析理论将在前几章中介绍。通常,表达式解析就是示例之一。

  3. 使用 JavaCC 或 ANTLR 生成词法分析器和解析器。我更喜欢 JavaCC,但各有各的。只需谷歌“javacc 示例”或“antlr 示例”。你会发现很多。

在 2 和 3 之间,我强烈推荐 3,即使你必须学习新技术。创建解析器生成器是有原因的。

还要注意,创建一个可以处理格式错误的输入的解析器(不仅仅是因为解析异常而失败)比编写一个只接受有效输入的解析器要复杂得多。您基本上必须编写一个语法来说明各种常见的语法错误。

更新:这是我使用 JavaCC 编写的表达式语言解析器的示例。语法松散地基于统一的表达语言。它应该能让你很好地了解你所面临的问题。

Contents of org.eclipse.sapphire/plugins/org.eclipse.sapphire.modeling/src/org/eclipse/sapphire/modeling/el/parser/internal/ExpressionLanguageParser.jj

【讨论】:

  • 感谢您为我提供所有可用的选项 - 在做出最终决定时真的很有帮助。我会尝试 Bill 首先发布的堆栈解决方案,因为我似乎可以更灵活地使用它,也因为我的一部分想自己做这一切,但如果它不起作用或如果我意识到我必须使用解析器生成器,我肯定会研究 JavaCC。非常感谢:)
【解决方案5】:

我们可以把给定的表达式 (5+2)*7 作为中缀

Infix  :     (5+2)*7
Prefix :     *+527

从上面我们知道了树的前序和中序遍历......我们可以很容易地从中构造树。 谢谢,

【讨论】:

  • link 获取转换提示。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-12-02
  • 2016-09-04
  • 1970-01-01
  • 2015-11-17
  • 2022-09-26
相关资源
最近更新 更多