第三次软工作业

发布时间 2023-09-29 03:29:21作者: NIOOIN

个人项目

软件工程 ?https://edu.cnblogs.com/campus/gdgy/CSGrade21-34
作业要求 ?https://edu.cnblogs.com/campus/gdgy/CSGrade21-34/homework/13025
GitHub链接 ?https://github.com/CloseCG/Four-Operators
作业目标 实现一个自动生成小学四则运算题目的命令行程序

PSP表格

*PSP2.1* *Personal Software Process Stages* *预估耗时(分钟)* *实际耗时(分钟)*
Planning 计划 20 30
· Estimate · 估计这个任务需要多少时间 200 240
Development 开发 240 200
· Analysis · 需求分析 (包括学习新技术) 80 200
· Design Spec · 生成设计文档 40 30
· Design Review · 设计复审 20 10
· Coding Standard · 代码规范 (为目前的开发制定合适的规范) 30 10
· Design · 具体设计 120 80
· Coding · 具体编码 120 120
· Code Review · 代码复审 30 10
· Test · 测试(自我测试,修改代码,提交修改) 60 200
Reporting 报告 60 70
· Test Repor · 测试报告 30 20
· Size Measurement · 计算工作量 10 10
· Postmortem & Process Improvement Plan · 事后总结, 并提出过程改进计划 20 10
· 合计 1120 1230

需求分析

粗略看下来,先站在宏观角度审视题目,我需要构造一个四则运算的表达式,存入Exercises.txt,然后计算出每道题的答案,存入Answers.txt文件中。前面完成了创造和计算功能,后面还要实现检验对错功能。按照生成的Exercis.txt计算结果,并且写入MyAnswers.txt,判断MyAnswers.txt的正确的题目数目,并写入Grade.txt。

  1. 根据上文,此处我们要构造四则运算的表达式

    1. 先拿出个普通四则运算看看:1+2+3=。可以看到我们目前有什么,数字和符号。那么我们何不将一个多项式拆分为两个part——Number和Operator,每个part都有一个数组存放他们的值。

    2. 注意,我们始终要明确自己的大方向是创造给人看的东西,也就是我们要在这里创建一个字符串以便写入文件。何不设置一个数组呢,每个element存放一个字符,将数组的元素全部输入到文件去,不就打印出“1+2+3=”了吗

    3. 现在有了container和element,就要想想以下几个问题:(1)怎么往容器里面放元素?即如何生成多项式 (2)如何计算并得出结果?(3)是否可以优化?

      1. 第一个问题貌似有点复杂,脑袋a cloud of浆糊,先想想我会遇到什么情况:生成什么数字?生成什么运算符?生成多少个运算符?出现括号咋办?如何保证表达式不重复 ?如何保证不出现负数?即若出现e1− e2的子表达式,那么e1≥ e2

        1. 创建一个Number类,其中包含一个double类型的属性为value,value自然是由我给出的上限来限定,注意当我给的自然数为10时,随机产生的实数∈[0,10)即可,但为了考虑到真分数这好哥们,我的新思路是:定义一个value存放真正的值,定义一个form存放它的形式,而为了便于计算,所有的数即便是自然数也表示为"10/1",而且混合数也表示为"10/7",具体原因见下文。

        2. 创建一个Operator类,其中一个int类型的属性value,其中0:1:2:3 = +:-:×:÷;还有一个属性是isNeedBracket,表明这个运算符在打印的时候是否需要在外层套一个括号

        3. 生成的运算符数量∈[1,3]

        4. 出现括号就很复杂了,虽然但是,经过一通乱搞我还是想到了个不错的解法:

          • 定义一个类MathExpression类,这个类有两个属性value和formOfFormula,Number类型的value记录当前表达式的值,而String类型的formOfFormula记录表达式的形式,如"1+2×3"、"(1+1)"或"1*(1+1)"等等

          • 直接举个例子:

            a+b-c×d×e÷f×g -> 不带括号时,左式运算符的优先级(小为高)为:5 6 1 2 3 4

            假设我随机修改了优先级(随机套括号),优先级变为:1 2 5 8 4 3

            你会发现有括号后表达式变为:((a+b)-c)×d×e÷(f×g)

            原优先级: 5 6 1 2 3 4

            现在优先级: 1 2 5 6 4 3

            一个规律:优先级比原来大的都套上了括号,很简单的道理嘛:想要逆天改命只有叠buff,所以想提高正常情况下的优先级都需要套括号(我之所以研究这个规律是为了打印的时候让括号出现在合适的位置,计算的时候只需要关注优先级)

            OK啊,现在记住这个规律即可,优先级升高了的都需要套括号,以便计算的时候用

        5. 不重复还挺复杂的

          • 其实不重复的本意就是防止xxs算一个表达式时每一个步骤其实都是重复的,这样根本就没有锻炼作用。举个例子,3+(2+1)和1+2+3,xxs通过这两个表达式都只学会了1+2=3,3+3=6

            而这个地方与我的MathExpression思想不谋而合,ME类记载了每一个步骤的结果,如果有两个算式的MER数组(见下文)包含的元素完全一致,其实就等于这xxs啥没学到,那就是重复的。

            为什么不需要连顺序都完全对应呢?1÷1×1与1×1÷1,顺序不一致,但是都只学到了1÷1和1×1

        6. 不出现负数

          • 有个Util工具类专门负责计算,在相减的时候会检查,如果出现了负数就重新生成表达式即可
      2. 计算结果的话,有几个需要考虑的问题:(1)如何保证e1÷ e2的子表达式结果是真分数?(2)如何优先计算括号的 (3)如何保证不会出现1÷0

        1. 结果要为真分数

          • 那么我就逮着分数算,算出一大坨分数也无所谓,只在最后打印的时候化简,具体化简有"10/1化为10","10/7化为1'3/7"
        2. 保证1÷0不存在

          • 如上,在相除之前会检查,如果分母出现为0的情况,重新生成表达式即可
        3. 计算括号的有点抽象嗷

          • 记得我们说MathExpression具有两个属性吗,value保留计算的结果,formOfFormula存储表达式的形式。

            计算时按照优先级,从高到低(其中1为最高优先级,以此类推)计算,举个详细的例子:

            ((a+b)-c)×d×e÷(f×g)

            1.为Number数组的每个元素new一个MathExpression,其中value=Number类,而formOfFormula即为它们自身,如"a"、"b",让它们组成一个数组

            从此我们只关注这个MathExpression数组和Operator数组了

            2.优先计算a+b(分别有真正的值和分数的形式),new一个MathExpresstion对象,名为ME1,假设得出值为v1,放入ME1的value属性,然后检测"+",发现它改命了,那么就需要套括号,所以存入formOfFormula的值为="(a+b)"

            因此MathExpression数组的a和b对应的MathExpression对象被删除,第一个元素就变为了ME,而符号数组的第一个+号也被删除。并且将这个玩意存到一个MER数组里面为了检查重复而以做备用

            让每个元素都是ME对象有一个好处,那就是如果直接使用Number数组,出现了Number-ME,我还需要额外判断一下"-"的那边是ME,再进行合并。而如果是ME1-ME2,合并后ME的字符串=前面ME1的fOF属性+"-"+ME2的fOF属性,很方便

            3.依次计算,最终会保留下来一个唯一的MathExpresstion对象,它存储了结果和表达式的样式

      3. 优化的话就别用数组,用ArrayList,楽

  2. 当command line没有参数不为自然数,出现异常提醒

  3. 按照先后顺序从简入手,写文件不难,但是要注意格式:

    1. 小数要表现为真分数,并且有1'1/2这种表现形式
    2. 四则运算符和等号前后都有空格
    3. 每个题目有编号+空格,如1.+空格(空格这不是Markdown的锅吗)
  4. 输出到Grade.txt的格式如下,注意空格

    Correct: 5 (1, 3, 5, 7, 9)

    Wrong: 5 (2, 4, 6, 8, 10)

模块接口设计和代码说明

本文的全部思路都已经在需求分析中详细叙述,此处就不做过多说明了。见代码即可。

粗讲一下类吧

ArgumentsException:处理参数异常

Figure:代表了每个数值

MainClass:主要执行各函数调用的类

MathExpression:对应表达式

Operator:运算符

Util:工具类

性能改进

性能界面

10个测试



异常处理

主要有一个检测参数是否为空的异常处理

public ArgumentsException() {
}

/**
 * 打印参数输入的异常
 * @param message 输入参数错误的信息
 */
public ArgumentsException(String message) {
    super(message);
}

小结

通过本次实现我收获巨大。但由于记错提交日期时间太紧,尽管完成了代码,但其他的描述就过于水了。不过这次我的确做到了真正的需求分析。并且在有限时间内完成了任务。