GitHub项目地址

https://github.com/Cynnn/Arithmatic

PSP2.1表格

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

一、项目描述与实现功能


1. 从《构建之法》第一章的 “程序” 例子出发,完成一个能自动生成小学四则运算题目的命令行 “软件”。

2. 实现的功能有:

  • 随机生成四则运算题目,操作数、运算符的种类和顺序都随机生成。
  • 支持整数和真分数的四则运算。
  • 能处理用户的输入,并判断对错,打分统计正确率。
  • 可以使用 -n 参数控制生成题目的个数。
  • 支持带括号的多元复合运算
  • 运算符个数随机生成(考虑小学生运算复杂度,范围在1~10)。

二、解题思路


1. 解题步骤

看到这个题目,我首先想到的是在《数据结构》课程中利用栈操作来对多项式进行求值,接下来,通过对其他需要实现的功能分析过后,我按照以下循序渐进的步骤来完成本次项目:

整数四则运算 ----> 支持真分数运算 ----> 随机生成四则运算式 ----> 判断用户输入对错并打分

2. 核心问题:

  • 中缀表达式求值
    之前学过的中缀表达式求值是利用栈先将其变为后缀表达式,再进行求值。我抱着有没有更简单易懂的方法的想法在网上搜索资料,然后看到了老师在博客留言中分享的链接(http://hczhcz.github.io/2014/02/27/shunting-yard-algorithm.html),其中谈到了调度场算法,学习过后我认为非常值得一试,于是本项目中主要用到的就是调度场算法的思想。
    调度场算法:建立运算符和操作数两个堆栈,从左向右扫描表达式,遇到数字进行压栈操作。若遇到符号,则判断其与栈顶符号的优先级,是右括号或者优先级低于栈顶符号,则栈顶元素依次出栈并进行计算,并将当前符号进栈,计算出的数字作为操作数也进行压栈操作,一直到运算符栈的栈为空,则操作数栈的栈顶元素出栈为最终结果。
  • 分数运算
    对于分数运算的问题,我的处理方法是:自定义一个分数类(包括分子和分母),对于整数和分数,都当作分数来处理(整数的分母为1)。先根据简单粗暴的运算法则计算出结果,最后再约分。
  • 随机生成带括号的四则运算式
    这是我最后一个完成的功能,之前把生成括号考虑的太简单,真正实现起来才意识到有很多限制条件。最终我采用的方法是:

    • 从左到右扫描操作数,通过随机数决定是否生成左括号,如果生成左括号,则再此操作数的后面选择一个操作数生成右括号。
    • 同理,从右到左扫描操作数,通过随机数决定是否生成右括号,如果生成右括号,则在此操作数的前面选择一个操作数生成左括号。
    • 需要注意的是:在此我定义了左括号标记数组右括号标记数组,对某个操作数添加左括号(右括号)必须在其没有右括号(左括号)的情况下才能添加。

三、设计实现过程


1. 类和方法定义

Class Method Description
Evaluate类 main() main()方法
evaluateAlgorithm() 调度场算法
JudgePriority() 判断优先级
caculate() 四则运算法则
GCD() 辗转相除法对分数约分
random() 随机生成四则运算式
Fraction类 Fraction() 构造方法

2. 主要数据结构

Stack<Character> ops = new Stack<Character>();    //运算符栈
Stack<Fraction> vals = new Stack<Fraction>();     //操作数栈
String[] op = new String[opnumber];               //运算符数组
String[] val = new String[valnumber];             //操作数数组

3. 实现过程

4.运行结果

  • 考虑到小学生的计算能力,在此设置的操作数取值范围为:0~10;运算符的数量为1~10。

四、关键代码说明


  • 调度场算法
public static String evaluateAlgorithm(String s) {Stack<Character> ops = new Stack<Character>();  //运算符栈Stack<Fraction> vals = new Stack<Fraction>();   //操作数栈for(int i=0;i<s.length();i++) {char s1 = s.charAt(i);//左括号入栈 if (s1 == '(') ops.push(s1);//右括号把之前的数和运算符出栈进行运算else if(s1 == ')') {while(ops.peek()!= '(') {int result[] = new int[2];Fraction a = vals.pop();Fraction b = vals.pop();result = caculate(ops.pop(),a.getNumerator(),a.getDenominator(),b.getNumerator(),b.getDenominator());vals.push(new Fraction(result[0],result[1]));}ops.pop();}else if(s1 == '+' || s1 == '-' || s1 == '*' || s1 == '÷') {  //遇到运算符的情况while(!ops.empty() && JudgePriority(s1,ops.peek())) {    //判断运算符的优先级int result[] = new int[2];Fraction a = vals.pop();Fraction b = vals.pop();//当前运算符如果比栈顶运算符的优先级高,将之前的运算符和数字出栈进行运算result = caculate(ops.pop(),a.getNumerator(),a.getDenominator(),b.getNumerator(),b.getDenominator()); vals.push(new Fraction(result[0],result[1]));  //将计算出的数字入栈              }ops.push(s1);}else {if (s1 >= '0' && s1 <= '9') {     //操作数入栈StringBuffer buf = new StringBuffer();while (i < s.length() && ((s.charAt(i) >='0' && s.charAt(i) <= '9') || s.charAt(i) == '/')) buf.append(s.charAt(i++));                              i--;String s2 = buf.toString();int flag = s2.length();for(int j=0;j<s2.length();j++) {  //寻找分号的位置if(s2.charAt(j) == '/') flag = j;}StringBuffer buf1 = new StringBuffer();StringBuffer buf2 = new StringBuffer();for(int k=0;k<flag;k++) {        //分号之前的是分子buf1.append(s2.charAt(k));}if(flag != s2.length() ) {       //分号后面是分母for(int k=flag+1;k<s2.length();k++)buf2.append(s2.charAt(k));          }//整数的分母是1else buf2.append('1');//入栈vals.push(new Fraction(Integer.parseInt(buf1.toString()),Integer.parseInt(buf2.toString())));}} }while(!ops.empty()) {int result[] = new int[2];Fraction a = vals.pop();Fraction b = vals.pop();result = caculate(ops.pop(),a.getNumerator(),a.getDenominator(),b.getNumerator(),b.getDenominator());vals.push(new Fraction(result[0],result[1]));}Fraction result = vals.pop();//最大公约数int k = GCD(result.numerator,result.denominator);//如果分母为1,只输出分子String rightResult;if(result.denominator/k == 1) {rightResult = result.numerator/k + "";}else { //输出分数rightResult = result.numerator/k+"/"+result.denominator/k + "";}return rightResult;}
  • 四则运算法则(由于随机生成操作数时限制了不能为0,所以在此没有考虑除数为0的情况)
//对numerator1/denominator1和numerator2/denominator2两个操作数进行运算
public  static int[] caculate(char op, int numerator1, int denominator1, int numerator2, int denominator2) { int[] result = new int[2];switch (op) {case '+': result[0] = numerator1*denominator2 + numerator2*denominator1; result[1]= denominator1*denominator2;return result;case '-':result[0] = numerator2*denominator1 - numerator1*denominator2; result[1]= denominator1*denominator2;return result;case '*':result[0] = numerator1*numerator2; result[1] = denominator1*denominator2;return result;case '÷':result[0] = numerator2*denominator1; result[1] = numerator1*denominator2;return result;}return result;}
  • 随机生成带括号的四则运算式
int m = (int)(Math.random()*10) % 2;if(m == 0) { //产生带括号的运算式int o = (int)(Math.random()*10) % 2;if (o == 0) { //先插入左括号,再插入右括号int[] lval1 = new int[valnumber]; //左括号标记数组int[] rval1 = new int[valnumber]; //右括号标记数组for (int k=0;k<valnumber-1;k++) {                   int n = (int)(Math.random()*10) % 2;if(n == 0 && rval1[k] != 1) {lval1[k] = 1;           //标记为有左括号val[k] = "(" + val[k];  //操作数之前加上左括号int c = valnumber - 1;//找右括号的位置,必须在左括号的后面int d = bracket1.nextInt(c)%(c-k) + (k+1);//如果当前操作数之前有左括号,需要重新生成运算式while (lval1[d] == 1) d = bracket1.nextInt(c)%(c-k) + (k+1);val[d] = val[d] +")";rval1[d] = 1;          //标记为有右括号} }} 

五、测试运行


  • 单元测试(Junit)

  • 代码覆盖率(EclEmma插件)




六、项目小结


  • 寻找bug之路…任重而道远

    最终的总结部分我要先把遇到的bug记录下来,在这次的项目实践中体会到无论对自己的代码怎样地信心满满,在调试过程中总会有这样那样的问题发生,有时一个不经意的小问题会折磨到令我“怀疑人生”,可见一个优秀的程序员还是要有善于发现bug的“火眼金睛”。

  1. 提示空栈异常
result = caculate(ops.pop(),vals.pop().getNumerator(),vals.pop().getDenominator(),vals.pop().getNumerator(),vals.pop().getDenominator())

原因是只顾赋值操作,没有意识到每次调用vals.pop()即为出栈,之后修改为:

Fraction a = vals.pop();
Fraction b = vals.pop();
result = caculate(ops.pop(),a.getNumerator(),a.getDenominator(),b.getNumerator(),b.getDenominator());
  1. 数组的初始化操作放到了循环中,导致之后的赋值部分没有意义,因为每次循环都会初始化数组。
  • 收获:

  1. 第一次使用GitHub对项目做版本控制,相信之后会更加熟练地使用它。
  2. 通过对《构建之法》第二章的学习,我对个人软件开发流程(PSP)有了全面的认识,并且了解到测试的重要性,本项目中我学会使用Junit,EclEmma插件对代码进行测试,对代码的执行过程更加了解。
  3. 学习到java命令行参数的使用。
  • 不足:

  1. 在测试方面仍有不足,本项目中没有用到效能测试方法,在以后的学习过程中会逐步改进。
  2. 对一些问题考虑不够全面,并且通过PSP表格也可看出对项目各方面的时间估计也有很大的偏差。

PS: 最后要特别感谢编程互助小组的D同学,不厌其烦地为我解答问题,从他身上我学到了很多:D。

转载于:https://www.cnblogs.com/cyan0629/p/7597684.html

2017202110104-高级软件工程第二次作业个人项目之-四则运算生成程序相关推荐

  1. 高级软件工程第二次作业

    1.Github地址 xinz 2.解题思路: 一开始拿到这道题的时候,对数独的概念不是那么清晰.通过题目中百度百科的概念,即被概括成满足以下三个条件的数字范围为1-9的棋盘:(1)每一行不能有重复的 ...

  2. 软件工程实践2017第二次作业-----个人项目实战之数独

    软件工程实践2017第二次作业-----个人项目实战之数独 最后一门考试2017.9.16 github地址:https://github.com/ssuo/shudu 题目地址:http://www ...

  3. 软件工程 第二周作业

    ##软件工程第二周作业 提出问题 1. 一般来说,想要自己的程序跑得又快又好,就要减少函数的反复调用,但有所得则必有所失,效能提高就有可能伴随着程序的稳定性的降低,这两者应该如何权衡呢? 2. 关于5 ...

  4. 第二次作业— —结对项目

    第二次作业- -结对项目 标签(空格分隔): 需求分析与原型模型设计 结对成员: 031302610 黄志鹏 031302603 陈波 1.样本实例 一个老师的迫切需求----开课报课之繁琐教师开课报 ...

  5. 软件工程第二次作业——模仿网站

    我以我的JS发誓 我以我的解释器宣告 将世界上所有的HTML汇聚 将世界上应有之css样式改变 集中所有的解释器和JS 将这网页为我所用 the Teleport Ultra 倾听我内心的愿望 让它实 ...

  6. 北航2022软件工程第二次作业——产品评测、分析与规划

    软件工程第二次分析作业 项目 内容 这个作业属于哪个课程 北京航空航天大学2022春季软件工程(罗杰 任健) 这个作业的要求在哪里 个人阅读作业-软件案例分析 我在这个课程的目标是 学习软件工程的基础 ...

  7. 2022师大高级软件工程博客作业

    项目 内容 这个作业属于哪个课程 2022年师大高级软件工程 这个作业的要求在哪里 软件工程 案例分析作业 我在这个课程的目标是 通过对软件工程的深入了解,深入分析市面已经成功的和未成功的软件,全面的 ...

  8. 第二次作业——个人项目实战

    本次作业的项目地址:Github仓库地址 解题思路 嗯,这次的作业要求是 利用程序随机构造出N个已解答的数独棋盘 刚开始看到的时候可以说是非常茫然的,数独以前虽然玩过,但要用程序来构造那就是完全的另一 ...

  9. 2016福州大学软件工程第二次作业成绩

    第二次作业为结对项目之需求分析与原型设计 以下为作业成绩: 学号 姓名 昵称 评分 031402524 王智强 iphone2s 9 031402509 胡泽善 aiprogram 9 0314025 ...

最新文章

  1. 查看SQL Server Resource Database以及修改系统表
  2. 干货 | 深入浅出分销体系
  3. python format 字典_python 用字典格式化字符串
  4. Excutor线程池
  5. python io操作有什么_Python笔记:文件IO操作
  6. java AST 表达式_Atitti.java exp ast java表达式语法ast构造器
  7. WebAssembly的未来:潜在新特性一览
  8. 如何压缩pdf文件的大小?
  9. 史上最详细蝉道开源安装步骤。
  10. 顺利通过2021年上工信部的网络工程师考试,在此感悟一下
  11. 计算机面试专业英语词汇,英语面试中常用高频词汇
  12. 因果效应,典型模型及wasserstein距离, BNN,CFR,SITE,NetDeconf
  13. [django]从前端返回字符串,后端转换为字典,执行数据添加操作
  14. Android——TextView指定字符串颜色高亮,实现类似微信、支付宝搜索结果中搜索字段高亮的效果
  15. springboot毕设项目同城上门喂遛宠物预约vfo34(java+VUE+Mybatis+Maven+Mysql)
  16. 安装mathtype6.9时显示缺少MT Extra字体问题
  17. 图形硬件介绍——GPU/帧缓存
  18. 限制局域网网速_过年总有亲戚蹭WIFI,设置访客网络,让网速不再卡
  19. 松勤性能测试项目实战(监控+分析+调优)集训营学习笔记
  20. goolge安装插件

热门文章

  1. python免费教学视频教程-Python免费教程_Python免费视频教程大全_易玩网
  2. stm32跑python-简易数据采集分析流程.stm32+python
  3. python零基础怎么学-零基础如何入门Python
  4. python怎么识别拼音-python获取一组汉字拼音首字母的方法
  5. python不能处理excel文件-python处理Excel文件的几个模块
  6. codeforces round25
  7. jvm--动态对象年龄判定
  8. Python中做接口自动化如何读取配置ini文件
  9. WSO2 API Manager安装部署配置
  10. css样式继承规则详解