Case Study
来源:Lambert, K.A., 2018. Fundamentals Of Python: Data Structures, 2nd ed. Cengage Learning.
目标
该程序允许用户输入一个任意的后缀表达式,并显示表达式的值或错误消息(如果表达式无效)。其核心是展示栈的应用。
基本要求
用户应该能够测试各种后缀表达式,同时保留结果的记录。表达式中的错误不应停止程序,而应生成消息,以便让用户了解和评估过程中的错误位置。基于这些要求,设计如下用户界面:
1请输入后缀表达式: 6 2 5 + *
2
3您输入的后缀表达式为: 6 2 5 + *
4其结果为: 42
5________________________________________
6
7
8请输入后缀表达式: 10 2 300 *+ 20/
9
10您输入的后缀表达式为: 10 2 300 * + 20 /
11其结果为: 30
12________________________________________
13
14
15请输入后缀表达式: 3 + 4
16
17您输入的后缀表达式为: 3 + 4
18错误: 栈上的操作数太少
19
20处理的表达式部分: 3 +
21栈上的操作数: 3
22________________________________________
23
24
25请输入后缀表达式: 5 6 %
26
27您输入的后缀表达式为: 5 6
28错误: 栈上的操作数太多
29
30处理的表达式部分: 5 6
31栈上的操作数: 5 6
32________________________________________
33
34
35请输入后缀表达式:
用户在终端中运行该程序时,会显示 请输入后缀表达式:
这样的提示,让用户在冒号 :
后输入其需要计算结果的后缀表达式。用户输入的后缀表达式应该被限制在该冒号后的一行文本中,表达式的每个 token 之间可以有任意的空格,相邻的操作数之间应该有一些空白。
当用户按下键盘上的 Enter 或 Return 键后,由用户输入的后缀表达式将被重新显示,例如: 您输入的后缀表达式为:6 2 5 + *
。注意,由程序显示的后缀表达式应包含提示文本,如此处的 您输入的后缀表达式为:
。并且,显示的后缀表达式中的各个 token 间应该恰好有一个空格。
如果用户输入了正确格式的后缀表达式,则在重新显示的后缀表达式的后一行显示结果,否则,应该显示相应的错误提示。然后显示另一个表达式的提示。
程序应检测并报告“所有”输入错误,无论是故意的还是无意的。一下是一些常见错误包括:
- 表达式包含太多操作数;即,当遇到表达式结束时,栈上有多个操作数。
- 表达式包含太少操作数;即,当遇到操作符时,栈上操作数少于两个。
- 表达式包含无法识别的 token。程序期望表达式由整数、四个算术操作符(+、-、*、/)和空格(空格或制表符)组成。其他任何内容都是无法识别的。
- 表达式包含除以 0 的情况。
下面是一些示例,展示了以上错误类型及其相应的错误消息:
1请输入后缀表达式:
2错误: 您没有输入任何后缀表达式,请输入有效的后缀表达式。
3________________________________________
4
5
6请输入后缀表达式: 1 2 3 +
7
8您输入的后缀表达式为: 1 2 3 +
9错误: 栈上的操作数太多
10
11处理的表达式部分: 1 2 3 +
12栈上的操作数: 1 5
13________________________________________
14
15
16请输入后缀表达式: 1 + 2 3 4 *
17
18您输入的后缀表达式为: 1 + 2 3 4 *
19错误: 栈上的操作数太少
20
21处理的表达式部分: 1 +
22栈上的操作数: 1
23________________________________________
24
25
26请输入后缀表达式: 1 2 % 3 +
27
28您输入的后缀表达式为: 1 2 3 +
29错误: 栈上的操作数太多
30
31处理的表达式部分: 1 2 3 +
32栈上的操作数: 1 5
33________________________________________
34
35
36请输入后缀表达式: 1 2 0 / +
37
38您输入的后缀表达式为: 1 2 0 / +
39错误: 除以零
40
41处理的表达式部分: 1 2 0 /
42栈上的操作数: 1
43________________________________________
在该案例中,token 指的是表达式中的基本元素或组成部分。具体来说,token 是表达式中的一个操作数(如数字)或操作符(如 +、-、*、/)。通过将表达式分解成一个个 token,程序可以逐步分析和处理表达式的各个部分,以便进行求值或其他操作。
程序设计
为了构建一个能够处理后缀表达式并输出结果或错误信息的程序,我们需要将整体任务分解为多个具体的部分,并将这些部分封装到适当的类中,以便于管理和维护。
首先,程序需要处理用户的输入,这意味着它必须能够从用户获取后缀表达式。为了实现这一点,我们需要一个用户界面类(PFEvaluatorView
)。这个类的 run
方法将负责与用户交互,提示用户输入后缀表达式,并将用户输入的字符串传递给后续的处理模块。
接下来,输入的后缀表达式字符串需要被解析为独立的 token。为了实现这一点,我们需要一个扫描器类(Scanner
)。该类在初始化时接收输入字符串(a_string
),并将其分割成一个个独立的 Token
对象。这些 Token
对象表示表达式中的每个元素(如操作数和操作符)。Scanner
类还应提供方法遍历这些 token,以便后续的处理模块可以逐个获取和处理它们。
在解析 token 之后,我们需要一个类来表示和管理这些 token。Token
类的设计目标就是封装表达式中的每个基本元素。每个 Token
对象都有类型和值两个属性。类型属性表示 token 是操作数还是操作符,如果是操作符,则进一步区分是哪种操作符;值属性则存储操作数的具体数值或操作符的字符表示。此外,Token
类还提供了另外一些方法,如判断 token 是否为操作符、获取 token 类型和值等。
解析和表示 token 后,程序需要对输入的表达式进行格式化,使其显示更加规范。这可以有一个新类(PFEvaluatorModel)来处理的。该类的 format
方法接收一个表达式字符串(a_string
),并使用 Scanner
类将其解析为 token 序列,然后将这些 token 格式化为标准形式,并返回格式化后的字符串。
表达式格式化之后,接下来是核心的求值过程。这可以由 PFEvaluator
类来完成。该类使用栈结构来求值后缀表达式。其 evaluate
方法通过遍历由 Scanner
类提供的 token
序列,根据操作符的类型执行相应的计算。具体地说,当遇到操作数时,将其压入栈中;当遇到操作符时,从栈中弹出相应数量的操作数,执行计算,并将结果重新压入栈中。在此过程中,该类还负责处理各种可能的错误情况,如操作数不足、未知 token 类型、除以 0 等。
为了捕获和处理求值过程中的错误,并向用户提供有用的错误信息,我们需要在 PFEvaluatorModel
类中实现相关功能。该类的 evaluation_status
方法负责返回评估器的状态信息,包括处理的表达式部分和栈内容。如果在求值过程中发生错误,PFEvaluator
类会抛出异常,PFEvaluatorModel
类捕获这些异常并生成相应的错误信息。
最后,求值结果或错误信息需要显示给用户。这由 PFEvaluatorView
类来完成。在用户输入表达式后,该类调用 PFEvaluatorModel
的 format
方法进行格式化,并调用 evaluate
方法进行求值,然后将结果或错误信息显示给用户。如果发生错误,PFEvaluatorView
类还会调用 PFEvaluatorModel
的 evaluation_status
方法来获取并显示详细的错误信息。
下面的时序图展示了程序各部分之间的动态交互过程,特别是用户输入表达式后如何通过各个类的协作来解析、格式化、评估表达式并最终显示结果或错误信息。
以下类图展示了程序中各个类及其属性、方法,以及类之间的关系。
程序实现
Exercise 1
编写一个程序,将中缀表达式转换为后缀表达式。这个程序应该使用案例中的 Token
类和 Scanner
类。此外,该程序应包含一个执行输入和输出的 main
函数和一个名为 IFToPFConverter
的类。主函数接收一个输入字符串并用它创建一个扫描器(Scanner
)。然后扫描器被作为参数传递给转换器对象(converter object)的构造函数 (convert
方法)。接着运行转换器对象的 convert
方法,使用本章描述的算法将中缀表达式转换为后缀表达式。这个方法返回一个表示后缀字符串的 token
列表。然后主函数显示这个字符串。你还应在 Token
类中定义一个新方法 get_precedence
,该方法返回一个整数,表示运算符的优先级。(注意:在这个项目中,假设用户总是输入语法正确的中缀表达式。)