• 个人作业1——四则运算题目生成程序(基于java)


    本次作业coding地址:https://coding.net/u/shizhuangde/p/Myapp/git

    题目描述

    从《构建之法》第一章的 “程序” 例子出发,像阿超那样,花二十分钟写一个能自动生成小学四则运算题目的命令行 “软件”,满足以下需求:

      - 除了整数以外,还要支持真分数的四则运算,真分数的运算,例如:1/6 + 1/8 = 7/24
      - 运算符为 +, −, ×, ÷
      - 并且要求能处理用户的输入,并判断对错,打分统计正确率。
      - 要求能处理用户输入的真分数, 如 1/2, 5/12 等  
      - 使用 -n 参数控制生成题目的个数,例如执行下面命令将生成10个题目 Myapp.exe -n 10
    

    计划

    PSP2.1 Personal Software Process Stages Time (%) Senior Student Time (%)
    Planning 计划 4 3
    · Estimate 估计这个任务需要多少时间 4 3
    Development 开发 86 88
    · Analysis 需求分析 (包括学习新技术) 4 6
    · Design Spec 生成设计文档 6 4
    · Design Review 设计复审 0 1
    · Coding Standard 代码规范 2 1
    · Design 具体设计 9 8
    · Coding 具体编码 54 67
    · Code Review 代码复审 4 4
    · Test (自我测试,修改代码,提交修改) 7 21
    Reporting 报告 10 9
    · 测试报告 2 2
    · 计算工作量 3 2
    · 并提出过程改进计划 5 5

    PSP分析:
    从百分比上看虽然与计划的百分比相似,但是实际上每个环节都花了比计划的时间多一些。

    • 由于是个人的作业所以觉得设计复审也不知道给谁看就计划了0,在实际开发过程中,参考了一下别人一些类是怎么设计的,对自己的类进行了一定方法的增加。

    • 编码这块,原先预计是120min,但是在实际中用了计划的两倍时间才完成。

    • 代码复审,我觉得虽然现在有写个4%,但是实际中我完成这个作业保证运行正常就提交了,其实应该有一些编的不完美的地方需要再花时间去修改

    开发

    需求分析

    通过题目进行需求分析

    • 自动出题
    • 只要进行加减乘除的运算(可以暂时不考虑带括号的运算)
    • 对答题结果进行正确判断,统计正确率
    • 能支持整数、真分数的计算
    • 在控制台进行使用Myapp.exe -n 10命令能执行

    经过对一个在当家教老师的朋友对项目进行更进一步的需求分析

    • 每道题在答错之后不直接给答案,给两次订正的机会
    • 答完输入的题目个数之后给出分数(订正的题目算答错)
    • 给完第一次分数之后进行错题的订正
    • 能选择难度

    通过计划和分析,结合个人的能力,先暂时开发一个能满足题目需求分析的基本功能版本,扩展功能版本希望能满足家教老师的需求。

    生成设计文档

    Fraction类:
    java原有的库中有整型,浮点型但是没有分数类,为了支持分数的计算,需要自己构造一个分数类。
    由于有些赋值或者随机生成的分数可能可以进行化简,所以需要增加约分(Reduce_deno)方法,而约分方法需要增加求分子分母的最大公约数(GCD)方法,
    另外为了能够对分母不同的分数进行加减运算需要增加,所以需要增加通分(same_deno)方法,而通分需要增加求两个分数的最小公倍数(LCM)方法。

    int_formula类
    整数式子类,即式子中进行运算的数均为整数。一个式子中包含两个整数、一个运算符号以及答案。主要方法是随机的生成一个式子类。
    针对除法的式子,答案可能是整数,浮点数,分数,所以需要有三个不同类型的结果,即result(整数),result1(浮点数),result2(分数)。-

    fra_formula类
    由类比得到fra_formula类

    代码规范

    缩进

    按照eclipse里换行自动标识的位置来写。
    

    断行

    一个语句一行的规则,括号可以和语句在同一行。
    

    命名

    具有一定意义的英文缩写来表示需要的变量名,方法。

    具体编码

    首先编写Fraction类,求最大公约数运用辗转相除法

       int GCD(int a,int b){
    		int c;
    		while(a%b!=0){
    			c=a%b;
    			a=b;
    			b=c;
    		}
    		return b;
    	}
    

    求最小公倍数只需要将两个数相乘除以他们的最大公约数即可

       int LCM(int a, int b){
    		return a*b/GCD(a,b);
    	}
    

    在编写分数通分方法时,由于f.numerator(分子)已经被改变,在赋值f.denominator(分母)用到的应该是原来的分子值,导致之后加减乘除方法中对结果进行约分时数据错误。
    错误代码:

    	Fraction reduce_deno(Fraction f){
    		f.numerator=f.numerator/GCD(f.numerator,f.denominator);
    		f.denominator=f.denominator/GCD(f.numerator, f.denominator);
    		return f;	
    	}
    

    修改后:

    	Fraction reduce_deno(Fraction f){
    		int deno = GCD(f.numerator, f.denominator);
    		f.numerator=f.numerator/deno;
    		f.denominator=f.denominator/deno;
    		return f;	
    	}
    

    剩下的分数的加减乘除方法只需要对约分和通分的方法进行一定的调用就可以实现,比较简单,就不一一列举了。

    个人认为整数和整数进行运算比分数与分数进行运算要简单,所以觉得先写一个只支持整数和整数进行运算的类(int_formula),然后再考虑分数之间的计算。做法很复杂,所以感觉自己的想法有点蠢,之后再考虑应该怎么改进。

    测试分数计算式子时发现,加减法运算时,如果题目中两个操作数分母不一样时,显示的题目就是通分过的结果:

    图1
    于是我在生成随机分数的位置添加了:

       System.out.printf("%d/%d %c %d/%d = ",f1.getNume(),f1.getDeno(),op,f2.getNume(),f2.getDeno());
    

    用于测试是哪个部分使两个操作数变成了通分形式,运行效果截图:

    图2
    可以看出生成的分数均为最简分数,通过逐一排除,原来在计算式子正确答案(reslut)时,加减法中有调用将两个分数化为通分形式的方法,导致分数的分子和分母被改变。
    原来的通分代码:

       	Fraction[] same_deno(Fraction f1,Fraction f2){
    		f1.numerator = f1.numerator*(LCM(f1.denominator, f2.denominator)/f1.denominator);
    		f2.numerator = f2.numerator*(LCM(f1.denominator, f2.denominator)/f2.denominator);
    		f1.denominator = LCM(f1.denominator, f2.denominator);
    		f2.denominator = LCM(f1.denominator, f2.denominator);
    		Fraction [] f = new Fraction[2];
    		f[0]=f1;
    		f[1]=f2;
    		return f;
    		
    	}
    

    修改后:

       Fraction[] same_deno(Fraction f1,Fraction f2){
    		Fraction [] f = new Fraction[2];
    		f[0]=new Fraction();
    		f[1]=new Fraction();
    		f[0].denominator =LCM(f1.denominator, f2.denominator);
    		f[1].denominator = LCM(f1.denominator, f2.denominator);
    		f[0].numerator = f1.numerator*(LCM(f1.denominator, f2.denominator)/f1.denominator);
    		f[1].numerator = f2.numerator*(LCM(f1.denominator, f2.denominator)/f2.denominator);
    		return f;
    		
    	}
    

    经过测试,生成的分数均为最简形式,但是当出现答案正确答案为分数时(即result2有值,result和result1为零),只要输入答案为0均会被判断为正确。所以修改了判断逻辑的代码,同时在分数类中又增加了一个判断分数是否能被化为整数的方法(isInteger()方法),在判断逻辑中,只有isInteger返回为真值时系统才会去比较answer1,answer分别与正确答案result1和result的值。

    经过修复此bug后,运行代码截图:

    图3

    测试时发现输入正确的浮点数类型答案时,被判为错误,于是重写了逻辑判断过程,支持输入保留两位小数的答案,运行截图:


    由于有增加对输出结果的约分,所以生成的式子中包括为整数与分数的混合计算。

    至此,已经可以分别实现生成单纯整数,或单纯分数的式子,接下来就是要实现混合出题。

    个人的思路是,将输入的出题数目拆分成一定的比例,一部分为纯整数式子的计算,一部分式子为包含分数的计算。

    由于出现输入“0/0”这种非法参数,所以增加了判断分数是否合法的方法(isLeagal()方法),相关代码:

       public boolean isLeagal(){
    		if(this.denominator == 0) 
    			return false;
    		else 
    			return true;
    	}
    

    报告

    测试报告

    并且实现了按比例出题,运行截图:

    基本功能编写完成。

    • 程序能够正常生成题目,并且不会出现非最简形式的分数
    • 对输入的答案兼容性高,当答案为整数时,则与之对应的小数,分数形式均判定为正确。
    • 能对输入的答案进行合法性的检查,当出现分母为0的情况时会提示用户重新输入

    事后总结

    1.这次的实验看似简单但是实现起来无比复杂,因为输入的答案会有很多种形式。对输入答案的处理首先需要对输入的内容进行预判,然后通过好多个if-else语句进行相应值的赋值。

    2.不得不说我的代码写得太难看了,现在要是回过头再看一下可能会说“这是哪个智障写的代码太难读了”。接下来要的代码复审主要是看看还有哪里需要改进的地方以及发现一些还未发现的bug

    3.这次注释写的有点少了,在必要的地方应该适当的加一些注释。

    4.希望再做一次类似的作业练练,不要太难的。接下来要结对编程,好慌。

    5.在做这次实验时学习了一些markdown的用法以及提交代码到coding的方法,很初级但是很有新鲜感。

  • 相关阅读:
    JNUOJ 1187
    JNUOJ 1184
    HDU 4848
    HDU 4849
    哈夫曼树和哈弗曼编码小记
    HDU 5726
    POJ 3368 & UVA 11235
    2016江苏省CPC省赛 I
    POJ 3928
    POJ 3067
  • 原文地址:https://www.cnblogs.com/shizhuangde/p/6508074.html
Copyright © 2020-2023  润新知