结对编程项目-四则运算整体总结
在这一周中,我和我的搭档完成项目并实现了拓展需求,现在我将本项目的成果做一个总结
一、需求分析
实现一个命令行程序,要求:
- 1、自动生成小学四则运算题目(加、减、乘、除)
- 2、生成题目去重
- 3、支持多运算符(比如生成包含100个运算符的题目)
- 4、支持真分数
- 5、统计正确率
- 6、处理生成题目并输出到文件,生成题目后从文件读入并判题
- 7、多语言支持
从题目要求可知,我们需要实现以下几个功能:
- 可通过输入设置语言
- 随机生成四则运算题目,通过循环使得生成的题目的计算过程不含负数且不重复,并可通过输入控制生成的题目数目以及数值的范围
- 能将生成的题目输出到文件,并从文件中获取题目实现自动计算从而判断回答是否正确
- 能将计算的结果转化为分数(如真分数五分之三表示为3/5,真分数二又八分之三表示为2’3/8。)
- 能计算通过统计正确的题数来计算正确率
二、设计思路
由于在上一阶段中我们已经实现中缀表达式的计算以及分数的转换,以上部分的详细思路请见博客结对编程项目-四则运算阶段性总结。
根据需求,考虑程序还需要新定义方法来实现随机生成运算题目、多种语言的支持。并且需要在主类中实现文件操作、生成题目的合法性检验、判断用户的回答是否正确以及调用其他的类的功能。于是我新定义了生成题目的Produce类和支持多语言的Language类,并重写了MyDCTester类的方法。
1、随机生成题目
- 从题目要求和实际情况得知生成题目的运算符多于一个少于三个。当运算符数目为1时不需要出现括号,当运算符数目为2时可以有0或1个括号,当运算符数目为3时可以有0到2个括号。于是我们使用随机数来控制括号和运算符的个数,然后再添加算式内的运算符和随机数,具体代码如下:
public class Produce {
int fuhao;//随机生成算式的符号总数
int kuohao; //随机生成的括号组数
int jiajian; //括号内随机生成的加减乘除
int size; //括号外随机生成的加减乘除
int shuzi; //随机生成数字
String m,n,p,q;//存放数字
String a,b,c;//存放符号
char suanshi [] = new char[12];
Random random = new Random();//使用Random类生成随机数
public String output(int range) {//变量range控制随机生成的数字在0到rang-1之间
String out;
fuhao=random.nextInt(3)+1;//随机生成运算符号和括号的总数,运算符号数最多三个,括号最多两组,根据这两个随机数结合实际情况随机生成题目
kuohao=random.nextInt(3);
m=suijishu(range);
n=suijishu(range);
p=suijishu(range);
q=suijishu(range);
a=suijihao();
b=suijihao();
c=suijihao();
if(fuhao==1) {//判断随机生成的符号总数
out= (m+a+n);
}
else if(fuhao==2){
if(kuohao==0){//判断随机生成的括号组数
out= (m+a+n+b+p);
}
else
{
out= ("("+m+a+n+")"+b+p);
}
}
else {
if(kuohao==0){
out= (m+a+n+b+p+c+q);
}
else if(kuohao==1)
{
out= ("("+m+a+n+b+p+")"+c+q);
}
else
{
out= ("("+m+a+n+")"+b+"("+p+c+q+")");
}
}
suanshi=out.toCharArray();
return out;
}
public String suijishu(int m){//生成随机数
shuzi=random.nextInt(m);
if(shuzi==0){
return this.score(m);
}
else{
return String.valueOf(shuzi);
}
}
public String suijihao() {//随机生成运算符号
size=random.nextInt(4);
String result;
switch (size){
case 0:
result="+";
break;
case 1:
result="-";
break;
case 2:
result="*";
break;
default:
result="÷";
}
return result;
}
public String score(int n) {//随机生成分数
int a, b, temp;
Random rand = new Random();
do {
a = rand.nextInt(n);
b = rand.nextInt(n);
} while (a == 0 || b == 0);
if (a > b) {
temp = a;
a = b;
b = temp;
}
return (a +"/"+ b);
}
}
2、多种语言的支持
我通过变量i和j控制需要输出提示语句的位置,并设置变量m选择语言,变量cha控制同一循环中的不同输出,变量time记录主方法中的循环次数从而改变输出的提示语句的位置,if-else语句将对应的不同位置的提示语句进行输出,实现的代码如下:
public class Language {
String ste;
int i=0,j=0;//通过i和j控制返回的字符串在主方法中的位置
public String lang(int m,boolean cha,int time) {//变量m选择语言,变量cha控制同一循环中输出的字符串,变量time记录主方法中的循环次数
if (i == 0) {
if (m == 1) {
ste=("输入题目数目");
} else if (m == 2) {
ste= ("輸入題目數目");
} else {
ste= ("Number of input questions");
}
i++;
} else if (i == 1) {
if (m == 1) {
ste= ("输入数据范围");
} else if (m == 2) {
ste= ("輸入數據範圍");
} else {
ste= ("Input data range");
}
i++;
} else if (i == 2) {
if(j<(time*2)) {//循环输出字符串"题目"与"你的回答"时控制字符串输出的位置
if (cha == true) {//控制输出同一循环中是输出"题目"还是输出"你的回答"
if (m == 1) {
ste = ("题目");
} else if (m == 2) {
ste = ("題目");
} else {
ste = ("The title");
}
} else {
if (m == 1) {
ste = ("你的回答是:");
} else if (m == 2) {
ste = ("妳的回答是:");
} else {
ste = ("Your answer is yes:");
}
}
j++;
}
if(j==(time*2)) {//结束循环则进入下一个需要不同语言的位置
i++;
}
}
else if (i == 3) {
if (m == 1) {
ste= ("结果:");
} else if (m == 2) {
ste= ("結果:");
} else {
ste= ("result");
}
j=0;
i++;
}
else if (i == 4) {
if(j<time) {
if (cha == true) {
if (m == 1) {
ste = ("题正确");
} else if (m == 2) {
ste = ("題正確");
} else {
ste = ("right");
}
} else {
if (m == 1) {
ste = ("题错误");
} else if (m == 2) {
ste = ("題錯誤");
} else {
ste = ("wrong");
}
}
j++;
}
if(j==time) {
i++;
}
} else {
if (m == 1) {
ste= ("正确率为:");
} else if (m == 2) {
ste= ("正確率為:");
} else {
ste= ("correct:");
}
i=0;
}
return ste;
}
}
3、主类中实现文件操作、生成题目的合法性检验及去重、判断用户的回答是否正确以及调用Produce、Language以及Sort类
当生成的题目中的任意两个数的计算结果为复数时,该题目不合法。将所有生成的题目的字符串去除括号并调用Sort类的对象排序后放入数组中,如果数组中有相同的字符串且两算式计算结果相同则将重复的题目重新生成。设计的代码及相关注释如下:
public class MyDCTester {
public static void main(String[] args) {
String expression, step,before,wukuohao;
StringTokenizer devide;
Sort sort = new Sort();
int a,b,c,d,rig=0, title, i, range,j,choose,k,find=0,get=0;
String quchong[];
String ceshi[];
Rational result = new Rational();
Rational now = new Rational();
Language lang = new Language();
Scanner in = new Scanner(System.in);
File file = new File("title.txt");
MyDC evaluator = new MyDC();
Produce produce = new Produce();
System.out.println("1:简体中文");
System.out.println("2:繁體中文");
System.out.println("3:English");
choose=in.nextInt(); //输入数字1、2、3选择语言
System.out.println(lang.lang(choose,true,0、));//提示输入题目数目
title = in.nextInt();
System.out.println(lang.lang(choose,true,0));//输入数据范围
range = in.nextInt();
in.nextLine(); //读取输入的回车
quchong = new String[title];
ceshi = new String[title];
String answer[] = new String[title];
try {//缓冲输入流需使用try-catch语句
for (i = 0; i < title; i++) {
do {//检验生成的计算题是否合法(是否需要计算复数)
before = produce.output(range);
expression=before.replaceAll("÷","/");//将生成的"÷"号转化为"/"号
wukuohao=expression.replaceAll("[[(])]","");//去除生成算式的括号
devide=new StringTokenizer(wukuohao);
quchong[i] = devide.nextToken();
while(devide.hasMoreTokens()==true){
quchong[i]=devide.nextToken()+quchong[i];//将去除括号后的算式记录在字符串数组中
}
quchong[i]=sort.charSort(quchong[i]);//对去除括号后的算式内的字符进行排序,使得具有相同字符个体的字符串具有相同的形式
if(i>=1){
for(k=i-1;k>=0;k--){
if (quchong[i]==quchong[k]) {
find=1;//记录是否有相同的字符串,若相同,find=1。
}
}
}
now = evaluator.evaluate(evaluator.trans(expression));
c = now.getNumerator();
d = now.getDenominator();
if (d == 1) {//计算生成题目的结果,作为去重的参考
ceshi[i] = String.valueOf(c);
} else if (c == 0) {
ceshi[i] = String.valueOf(0);
} else if (c > d) {
ceshi[i] = (c / d + "\'" + (c % d) + "/" + d);
} else {
ceshi[i] = (c + "/" + d);
}
if(i>=1){
for(k=i-1;k>=0;k--){
if (ceshi[i]==ceshi[k]) {
get=1;//若生成的算式与前面已生成算式的计算结果相同,get=1。
}
}
}
} while (c < 0||(find==1&&get==1));//判断生成的题目是否合法并去重
if (i == 0) {//若生成的是第一题,则刷新文件
Writer out = new FileWriter("title.txt");
BufferedWriter bufferWrite = new BufferedWriter(out);
bufferWrite.write(before);
bufferWrite.newLine();
bufferWrite.close();
out.close();
} else {//不是第一题则从文件后开始输入
Writer out = new FileWriter("title.txt", true);//将生成的题目输入到文件中
BufferedWriter bufferWrite = new BufferedWriter(out);
bufferWrite.write(before);
bufferWrite.newLine();
bufferWrite.close();
out.close();
}
System.out.println(lang.lang(choose,true,title) + (i + 1) + ":" + before);//将题目输出
System.out.println(lang.lang(choose,false,title));//根据所选语言输出“你的回答”
answer[i] = in.nextLine();//记录用户输入的回答
}
Reader input = new FileReader("title.txt");
BufferedReader bufferRead = new BufferedReader(input);
System.out.println();
System.out.println(lang.lang(choose,true,0));
for(j=0;j<title;j++){
result = evaluator.evaluate(evaluator.trans((bufferRead.readLine()).replaceAll("÷","/")));//从文件中获取题目,并将题目中的÷"号转化为"/"号
a = result.getNumerator();
b = result.getDenominator();
if (b == 1) {//计算生成题目的结果,并将结果转化为真分数
step = String.valueOf(a);
} else if (a == 0) {
step = String.valueOf(0);
} else if (a > b) {
step = (a / b + "\'" + (a % b) + "/" + b);
} else {
step = (a + "/" + b);
}
if(step.equals(answer[j])) {//判断是否正确,使用变量rig记录正确的题数
System.out.println((j+1)+lang.lang(choose,step.equals(answer[j]),title));
rig++;
}
else{
System.out.println((j+1)+lang.lang(choose,step.equals(answer[j]),title));
}
}
bufferRead.close();
input.close();
System.out.println(lang.lang(choose,true,0)+((float)rig/title)*100+"%");//输出正确率
} catch (IOException e) {
System.out.println(e.toString());
}
}
}
三、UML类图
四、实现过程中的关键代码解释
我已在设计思路部分给出了设计思路并对关键代码进行了注释。
五、运行过程截图
题目文件内容:
六、代码托管地址
七、遇到的困难及解决方法
- 问题一:在编写了底层类后编写了FileReader类后报错。
- 问题一解决办法:FileReader类定义出来后需要使用try-catch语句。
- 问题二:运行提示Stream closed。
- 问题二解决办法:将缓冲流的close方法置于字符流的close方法之前。
- 问题三:从文件获取算式时只能获取第一个算式。
- 问题三解决办法:在获取完所有算式前不要关闭输入流。
八、对结对的小伙伴做出评价
我的结对搭档是江野,在本周的结对过程中我们一起编程一同讨论,使得原本单调的项目变得不再枯燥,在这过程中我们互相都有所收获,希望下次还有合作的机会。
九、PSP
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | ||
· Estimate | · 估计这个任务需要多少时间 | 15 | 30 |
Development | 开发 | ||
· Analysis | · 需求分析 (包括学习新技术) | 35 | 45 |
· Design Spec | · 生成设计文档 | 45 | 45 |
· Design Review | · 设计复审 (和同事审核设计文档) | 30 | 25 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 30 | 35 |
· Design | · 具体设计 | 130 | 150 |
· Coding | · 具体编码 | 230 | 260 |
· Code Review | · 代码复审 | 35 | 45 |
· Test | · 测试(自我测试,修改代码,提交修改) | 30 | 35 |
Reporting | 报告 | 60 | 80 |
· Size Measurement | · 计算工作量 | 15 | 25 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 25 | 25 |
合计 | 680 | 800 |