小学生:你们是魔鬼吗?
一、项目说明
魔鬼成员:谢文柱 刘丽昀
魔鬼地址: https://github.com/onetrue/Arithemetic
实现一个自动生成小学四则运算题目的命令行程序。
需求:
1. 使用 -n 参数控制生成题目的个数,例如
Myapp.exe -n 10
将生成10个题目。
2. 使用 -r 参数控制题目中数值(自然数、真分数和真分数分母)的范围,例如
Myapp.exe -r 10
将生成10以内(不包括10)的四则运算题目。该参数可以设置为1或其他自然数。该参数必须给定,否则程序报错并给出帮助信息。
3. 生成的题目中计算过程不能产生负数,也就是说算术表达式中如果存在形如e1 − e2的子表达式,那么e1 ≥ e2。
4. 生成的题目中如果存在形如e1 ÷ e2的子表达式,那么其结果应是真分数。
5. 每道题目中出现的运算符个数不超过3个。
6. 程序一次运行生成的题目不能重复,即任何两道题目不能通过有限次交换+和×左右的算术表达式变换为同一道题目。
例如,23 + 45 = 和45 + 23 = 是重复的题 目,6 × 8 = 和8 × 6 = 也是重复的题目。3+(2+1)和1+2+3这两个题目是重复的,
由于+是左结合的,1+2+3等价于(1+2)+3,也就是3+(1+2),也就是3+(2+1)。但是1+2+3和3+2+1是不重复的两道题,因为
1+2+3等价于(1+2)+3,而3+2+1等价于(3+2)+1,它们之间不能通过有限次交换变成同一个题目。
生成的题目存入执行程序的当前目录下的Exercises.txt文件,格式如下:
1. 四则运算题目1
2. 四则运算题目2
……
其中真分数在输入输出时采用如下格式,真分数五分之三表示为3/5,真分数二又八分之三表示为2’3/8。
7. 在生成题目的同时,计算出所有题目的答案,并存入执行程序的当前目录下的Answers.txt文件,格式如下:
1. 答案1
2. 答案2
特别的,真分数的运算如下例所示:1/6 + 1/8 = 7/24。
8. 程序应能支持一万道题目的生成。
二、PSP
PSP2.1 |
Personal Software Process Stages |
预估耗时(分钟) |
实际耗时(分钟) |
Planning |
计划 |
30 |
30 |
· Estimate |
· 估计这个任务需要多少时间 |
10 |
30 |
Development |
开发 |
600 |
900 |
· Analysis |
· 需求分析 (包括学习新技术) |
60 |
120 |
· Design Spec |
· 生成设计文档 |
10 |
10 |
· Design Review |
· 设计复审 (和同事审核设计文档) |
60 |
60 |
· Coding Standard |
· 代码规范 (为目前的开发制定合适的规范) |
30 |
30 |
· Design |
· 具体设计 |
30 |
60 |
· Coding |
· 具体编码 |
1200 |
900 |
· Code Review |
· 代码复审 |
60 |
60 |
· Test |
· 测试(自我测试,修改代码,提交修改) |
30 |
30 |
Reporting |
报告 |
60 |
60 |
· Test Report |
· 测试报告 |
20 |
10 |
· Size Measurement |
· 计算工作量 |
10 |
20 |
· Postmortem & Process Improvement Plan |
· 事后总结, 并提出过程改进计划 |
30 |
60 |
合计 |
|
|
三、设计思路
- 要设计一个专门的结构体存储整个算式部分,结构体包含所有运算符和所有操作数。还有一个结构体专门存储答案。其中所有数都是分数形式表示,用一个包含两个整形的一位数组表示一个分数。
- 将整个软件逐步分成一个个很小的部分,逐一实现。先将生成器分成运算生成和结果计算两部分,运算生成再分为运算符生成和操作数生成,两个都用随机数随机生成。计算结果部分用循环判断运算符优先级并经行不同运算。
- 还需要一个输出表达函数将一个运算结构体转换为字符串,该函数需要判断每个分数是表示整数、真分数还是假分数,并转换为正确形式输出。
- 最后通过fopen等函数将表达式和结果分别输入到不同文件中。
四、具体代码实现
- 设计两个结构体专门存储整个算式和答案。
struct Problem//四则运算问题结构体 { int numoffuhao; int numofshu; char fuhao[3]; int shu[4][2]; }; struct Answer//四则运算答案结构体 { int fenzi; int fenmu; };
- 生成算式函数
void createProblem(Problem *p,int i,int r)//随机生成一个问题 { int fuhaoshu;//先随机确定符号数 fuhaoshu=rand()%3; fuhaoshu+=1; p[i].numoffuhao=fuhaoshu; p[i].numofshu=fuhaoshu+1; int j; int yueshu; for(j=0;j<fuhaoshu;j++)//循环生成所有随机符号 { p[i].fuhao[j]=createFuhao(); } for(j=0;j<fuhaoshu+1;j++)//循环生成所有随机数 { int k; k=rand()%2; if(k==0) { p[i].shu[j][0]=createSuijishu(r); p[i].shu[j][1]=1; }else { p[i].shu[j][0]=createSuijishu(r); p[i].shu[j][1]=createSuijishu(r); yueshu=gcd(p[i].shu[j][0],p[i].shu[j][1]); p[i].shu[j][0]/=yueshu; p[i].shu[j][1]/=yueshu; } } }
生成具体符号和具体操作数的函数,以及计算最大公约数的函数
char createFuhao()//随机生成运算符 { int i; i=rand()%4; if(i==0) return '+'; if(i==1) return '-'; if(i==2) return '*'; if(i==3) return '/'; } int createSuijishu(int r)//随机生成不大于r的随机数 { int i; do i=rand()%r; while(i==0); return i; } int gcd(int a,int b) { if(a>b) return (b>0)?gcd(b,a%b):a; return (a>0)?gcd(a,b%a):b; }
- 答案计算函数
void countAnswer(Problem* p,int i,Answer* a)//计算问题的答案 { int fuhaoshu; fuhaoshu=p[i].numoffuhao; char fuhaos[3]; int shus[4][2]; int temAns[2]; int j,k,l; int flag;//判断符号优先级 for(j=0;j<fuhaoshu;j++)//获取符号数组 { fuhaos[j]=p[i].fuhao[j]; } for(j=0;j<fuhaoshu+1;j++)//获取数字数组 { shus[j][0]=p[i].shu[j][0]; shus[j][1]=p[i].shu[j][1]; } for(j=0;j<fuhaoshu;j++)//循环进行一次运算 { flag=0; for(k=0;k<fuhaoshu-j;k++)//判断哪个运算优先 { if(fuhaos[k]=='*'||fuhaos[k]=='/') { flag=k; break; } } jisuan(fuhaos[flag],shus[flag],shus[flag+1],temAns); shus[flag][0]=temAns[0]; shus[flag][1]=temAns[1]; for(k=flag+1;k<fuhaoshu-j;k++) { fuhaos[k-1]=fuhaos[k]; shus[k][0]=shus[k+1][0]; shus[k][1]=shus[k+1][1]; } } a[i].fenzi=shus[0][0]; a[i].fenmu=shus[0][1]; }
- 算式结构体转换成字符串表达函数
void printProblem(Problem* p,int i,char *s)//将问题编程字符串形式 { char c[50]; char tc[10]; int j; for(j=0;j<p[i].numoffuhao;j++) { if(p[i].shu[j][1]==1) { itoa(p[i].shu[j][0],tc,10); strcat(c,tc); }else { if(p[i].shu[j][0]<p[i].shu[j][1]) { itoa(p[i].shu[j][0],tc,10); strcat(c,tc); strcat(c,"/"); itoa(p[i].shu[j][1],tc,10); strcat(c,tc); }else { int zhengshu,zhenfenzi,zhenfenmu; zhenfenmu=p[i].shu[j][1]; zhengshu=p[i].shu[j][0]/zhenfenmu; zhenfenzi=p[i].shu[j][0]%zhenfenmu; itoa(zhengshu,tc,10); strcat(c,tc); strcat(c,"'"); itoa(zhenfenzi,tc,10); strcat(c,tc); strcat(c,"/"); itoa(zhenfenmu,tc,10); strcat(c,tc); } } strcat(c," "); tc[0]=p[i].fuhao[j]; tc[1]='\0'; strcat(c,tc); strcat(c," "); } if(p[i].shu[j][1]==1) { itoa(p[i].shu[j][0],tc,10); strcat(c,tc); }else { if(p[i].shu[j][0]<p[i].shu[j][1]) { itoa(p[i].shu[j][0],tc,10); strcat(c,tc); strcat(c,"/"); itoa(p[i].shu[j][1],tc,10); strcat(c,tc); }else { int zhengshu,zhenfenzi,zhenfenmu; zhenfenmu=p[i].shu[j][1]; zhengshu=p[i].shu[j][0]/zhenfenmu; zhenfenzi=p[i].shu[j][0]%zhenfenmu; itoa(zhengshu,tc,10); strcat(c,tc); strcat(c,"'"); itoa(zhenfenzi,tc,10); strcat(c,tc); strcat(c,"/"); itoa(zhenfenmu,tc,10); strcat(c,tc); } } strcat(c," = "); strcpy(s,c); }
- 主函数
int main(int argc,char *argv[]) { srand(time(NULL)); FILE *fp1,*fp2; if((fp1=fopen("F:\\university\\大三第一学期\\课程\\软件工程\\第二次作业\\ArithmeticProgram\\ArithmeticProgram\\bin\\Debug\\Exercises.txt","w"))==NULL) {printf("can not open file\n"); exit(0); } if((fp2=fopen("F:\\university\\大三第一学期\\课程\\软件工程\\第二次作业\\ArithmeticProgram\\ArithmeticProgram\\bin\\Debug\\Answers.txt","w"))==NULL) {printf("can not open file\n"); exit(0); } int n,r; if((strcmp(argv[1],"-n")==0)&&(strcmp(argv[3],"-r")==0)) { n=atoi(argv[2]); r=atoi(argv[4]); } Problem *problems = (Problem*)malloc(n*sizeof(Problem));//创建一个问题数组,保存所有的问题 Answer *answers = (Answer*)malloc(n*sizeof(Answer));//创建一个答案数组,保存所有答案 int i; for(i=0;i<n;i++)//循环生成题目 { char s[50]; char s1[50]=""; do { createProblem(problems,i,r);//生成一个问题,存在问题结构体中 countAnswer(problems,i,answers);//计算这个问题的答案,存在答案结构体中 }while(answers[i].fenzi<=0||answers[i].fenmu<=0); printProblem(problems,i,s);//将问题转换为字符串 char tc[10]; if(answers[i].fenmu==1) { itoa(answers[i].fenzi,tc,10); strcat(s1,tc); }else { if(answers[i].fenmu>answers[i].fenzi) { itoa(answers[i].fenzi,tc,10); strcat(s1,tc); strcat(s1,"/"); itoa(answers[i].fenmu,tc,10); strcat(s1,tc); }else { int zhengshu,zhenfenzi,zhenfenmu; zhenfenmu=answers[i].fenmu; zhengshu=answers[i].fenzi/answers[i].fenmu; zhenfenzi=answers[i].fenzi%answers[i].fenmu; itoa(zhengshu,tc,10); strcat(s1,tc); strcat(s1,"'"); itoa(zhenfenzi,tc,10); strcat(s1,tc); strcat(s1,"/"); itoa(zhenfenmu,tc,10); strcat(s1,tc); } } printf("%d\t%s%s\n",i+1,s,s1); char bianhao[10]; itoa(i+1,bianhao,10); fputs(bianhao,fp1); fputs("\t",fp1); fputs(s,fp1); fputs("\n",fp1); fputs(bianhao,fp2); fputs("\t",fp2); fputs(s1,fp2); fputs("\n",fp2); strcpy(s1,""); } return 0; }
五、测试结果
输入命令
命令行结果
题目文件Exercise.txt
答案文件Answers.txt
六、项目小结
- 本次结对编程,我们采用先各自收集资料,思考方法,再一起线下讨论的方法。我们先根据题目自己独立思考编写思路,并上网收集资料,在线下讨论的时候,我们互相交流了思路,一开始谢文柱的思路是算术题是本质,操作数和运算符是核心,操作数本质上都是假分数,所以可以用特殊结构体存储算术题,并对结构体进行特殊运算和表达。刘丽昀的思路是表达式是核心,先判断数的类型(整数或分数),再根据不同的数的类型去实现表达式并且计算。最后进过激烈并深入的讨论,采用结构体实现会比较简洁,代码更容易实现,逻辑比较清晰严谨。在这一次讨论中我们能够获得多种实现方案,懂得在不同方案中选择更有优势的一个,并且在选择中学习对方思路中的优点,避开不足之处。
- 分工方面,谢文柱负责逻辑结构的整理,刘丽昀负责整体结构的梳理与调整。具体来讲就是刘丽昀负责编写软件的整体结构,并对其进行细分,而谢文柱这实现个别详细步骤的具体实现。在分工实现方面,我们懂得了,从另一个角度更能看出自己看不到的问题,从而能更快地解决问题。
- 在完成整个项目后,回顾整个项目,早期各自想思路时花费时间较多,也比较困难,在讨论之后的实现过程效率较单人思考会更高。首次合作,配合度也还不错,分工明确,效率高,但是由于单人思考时间花费的多了一些,导致后面的交流讨论不是很够,希望以后可以更合理的分配时间。