结对编程项目
项目参与者:
3118005119赵玮锋 3118005120郑堡恩
Github地址:https://github.com/FPXBao/WithZWF
1.需求分析:
实现一个自动生成小学四则运算题目的命令行程序(也可以用图像界面,具有相似功能)。
说明:
自然数:0, 1, 2, …。
真分数:1/2, 1/3, 2/3, 1/4, 1’1/2, …。
运算符:+, −, ×, ÷。括号:(, )。
等号:=。 分隔符:空格(用于四则运算符和等号前后)。
算术表达式:e = n | e1 + e2 | e1 − e2 | e1 × e2 | e1 ÷ e2 | (e),其中e, e1和e2为表达式,n为自然数或真分数。
四则运算题目:e = ,其中e为算术表达式。
需求:
使用 -n 参数控制生成题目的个数,例如:Myapp.exe -n 10
将生成10个题目。
使用 -r 参数控制题目中数值(自然数、真分数和真分数分母)的范围,例如:Myapp.exe -r 10
将生成10以内(不包括10)的四则运算题目。该参数可以设置为1或其他自然数。该参数必须给定,否则程序报错并给出帮助信息。
生成的题目中计算过程不能产生负数,也就是说算术表达式中如果存在形如e1− e2的子表达式,那么e1≥ e2。
生成的题目中如果存在形如e1÷ e2的子表达式,那么其结果应是真分数。
每道题目中出现的运算符个数不超过3个。
程序一次运行生成的题目不能重复,即任何两道题目不能通过有限次交换+和×左右的算术表达式变换为同一道题目。例如,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
四则运算题目2
……
其中真分数在输入输出时采用如下格式,真分数五分之三表示为3/5,真分数二又八分之三表示为2’3/8。
在生成题目的同时,计算出所有题目的答案,并存入执行程序的当前目录下的Answers.txt文件,格式如下:
答案1
答案2
特别的,真分数的运算如下例所示:1/6 + 1/8 = 7/24。
程序应能支持一万道题目的生成。
程序支持对给定的题目文件和答案文件,判定答案中的对错并进行数量统计,输入参数如下:
Myapp.exe -e <exercisefile>.txt -a <answerfile>.txt
统计结果输出到文件Grade.txt,格式如下:
Correct: 5 (1, 3, 5, 7, 9)
Wrong: 5 (2, 4, 6, 8, 10)
其中“:”后面的数字5表示对/错的题目的数量,括号内的是对/错题目的编号。为简单起见,假设输入的题目都是按照顺序编号的符合规范的题目。
2.PSP
| PSP2.1 | Personal Software Process Stages | 预估耗时(min) | 实际耗时(min) |
| Planning | 计划 | ||
| Estimate | 预估任务所需时间 | 2215 | 2420 |
| Development | 开发 | ||
| Analysis | 需求分析(包括学习新技术) | 120 | 120 |
| Design Spec | 生成设计文档 | 50 | 30 |
| Design Review | 设计复审 | 10 | 5 |
| Coding Standard | 代码规范 | 10 | 5 |
| Design | 具体设计 | 120 | 145 |
| Coding | 具体编码 | 1440 | 1600 |
| Code Review | 代码复审 | 60 | 60 |
| Test | 测试 | 300 | 350 |
| Reporting | 报告 | ||
| Test Report | 测试报告 | 60 | 80 |
| Size Measurement | 计算工作量 | 5 | 5 |
| Postmortem&Process Improvement Plan | 事后总结,并提出过程改进计划 | 40 | 20 |
| 合计 | 2215 | 2420 |
3.代码:
#include <stdio.h> #include <stdlib.h> #include <time.h> #include <string.h> #define NULL -1 typedef struct {//栈 char str[30]; int top; }Stack; void initialStack(Stack *S){//初始化栈 S->top=NULL; } int StackEmpty(Stack S){//栈的判空 if(S.top==NULL) return 1; return 0; } int StackFull(Stack S){//栈的判满 if(S.top==19) return 1; return 0; } void PushStack(Stack *S,char e){//入栈 if(StackFull(*S)) return ; S->str[++S->top]=e; } char PopStack(Stack *S){//出栈 if(StackEmpty(*S)) return 0; return S->str[S->top--]; }
符号优先级判断
int CharPriority(char ch){//符号优先级判断 if(ch=='(') return 0; if(ch=='+'||ch=='-') return 1; if(ch=='*'||ch=='/') return 2; if(ch==')') return 3; return 0; }
转逆波兰并计算结果
void turnRPNandCalculate(int *num,char *ch){//转逆波兰并计算结果 int i,j=0; char e; int num1[30]; char ch1[30]; for(i=0;i<30;i++){ num1[i]=-1; ch1[i]=' '; } Stack *s; s=(Stack*)malloc(sizeof(Stack)); initialStack(&s); FILE *fp; fp=fopen("Answers.txt","a"); for(i=0;i<30;i++){ if(num[i]!=-1&&ch[i]==' ') num1[j++]=num[i]; else if(num[i]==-1&&ch[i]=='(') PushStack(s,ch[i]); else if(num[i]==-1&&ch[i]==')'){ PushStack(s,ch[i]); while(s->top!=NULL){ e=PopStack(s); if(e!='('&&e!=')') ch1[j++]=e; if(e=='(') break; } } else if(ch[i]!=' '&&ch[i]!='='){ if(CharPriority(ch[i])<=CharPriority(s->str[s->top])){ while(s->top!=NULL&&(CharPriority(ch[i])<=CharPriority(s->str[s->top]))){ e=PopStack(s); if(e!='('&&e!=')') ch1[j++]=e; if(e=='(') break; } PushStack(s,ch[i]); } else PushStack(s,ch[i]); } } while(s->top!=NULL){ e=PopStack(s); ch1[j++]=e; } double x,y; double count[30]; for(i=0,j=0;i<30;i++){ if(num1[i]!=-1) count[j++]=num1[i]; if(ch1[i]=='+'||ch1[i]=='-'||ch1[i]=='*'||ch1[i]=='/'){ x=count[j-2]; y=count[j-1]; j-=2; switch(ch1[i]){ case '+':{count[j++]=x+y;break;} case '-':{count[j++]=x-y;break;} case '*':{count[j++]=x*y;break;} case '/':{count[j++]=x/y;break;} default: break; } } } fprintf(fp,"%f\n",count[0]); fclose(fp); }
提取数值和运算符
void getIntandChar(FILE *f,int *num,char *ch){//提取数值和运算符 char c[99]; int i,j,k,count=0; int tag=0; int LEN; fgets(c,99,f); j=0; LEN=strlen(c); c[LEN-1]='\0'; for(i=0;i<30;i++){ num[i]=-1; ch[i]=' '; } for(i=0,j=0;i<LEN;i++){//筛选数值和运算符 if(c[i]>='0'&&c[i]<='9'){ count*=10; count+=c[i]-'0'; tag=1; } else if((c[i]=='+'||c[i]=='-'||c[i]=='*'||c[i]=='/'||c[i]=='('||c[i]==')'||c[i]=='\''||c[i]=='=')&&tag==1){ num[j++]=count; count=0; ch[j++]=c[i]; tag=0; } else if((c[i]=='+'||c[i]=='-'||c[i]=='*'||c[i]=='/'||c[i]=='('||c[i]==')'||c[i]=='\''||c[i]=='=')&&tag==0) ch[j++]=c[i]; } }
将真分数化为假分数
void ChangeFraction(int *num,char *ch){//将真分数化为假分数 int i,j; for(i=0;i<30;i++){ if(ch[i]=='\''){ num[i+1]=num[i-1]*num[i+3]+num[i+1]; num[i-1]=-1; ch[i]=' '; } } }
生成随机四则运算
void CreateNum(int n,int r,FILE *f){//生成随机四则运算 int i,j,k,x,y; int count; int num[999]; int t[7]; int tag=0,tag1=0; int flag; char c[4]={'+','-','*','/'}; char c1[2]={'(',')'}; f=fopen("Exercises.txt","a"); if(f==NULL) printf("ERROR\n"); fclose(f); for(x=0;x<r;x++) num[x]=x; for(y=0;y<n;y++){ f=fopen("Exercises.txt","a"); for(i=0;i<8;i++){ t[i]=num[rand()%r]; } count=4; for(j=0,k=0;j<count;j++,k++){//4个运算数+3个运算符,若有括号count+1 if(rand()%2==1&&j<3&&tag1==0){//随机生成0和1,1表示添加括号,0则非 fprintf(f,"%c",c1[0]); tag1=1; flag=j; } if(rand()%2==1){//随机生成0和1,1表示抽取自然数,0表示抽取分数 fprintf(f,"%d",t[k]); tag++; }else{ if(t[k]>t[k+1]&&t[k]%t[k+1]!=0&&t[k+1]!=0){//检测是否需要化为真分数 fprintf(f,"%d'%d/%d",t[k]/t[k+1],t[k]%t[k+1],t[k+1]); k+=2; tag++; } else if(t[k]<t[k+1]&&t[k]!=0){//检测是否为真分数 fprintf(f,"%d/%d",t[k],t[k+1]); k+=2; tag++; }else{//若出现整除,则直接输出自然数 fprintf(f,"%d",t[k]); k++; tag++; } } if(tag1!=0&&flag<j){ fprintf(f,"%c",c1[1]); tag1=0; } if(tag%4!=0) fprintf(f,"%c",c[rand()%4]);//随机生成0到3之间的数,用以随机输出运算符 } fprintf(f,"=\n"); fclose(f); } }
检查答案
void checkAnswer(){//检查答案 FILE *f,*fp,*fy; f=fopen("Answers.txt","r"); fp=fopen("StuAnswers.txt","r"); char a[50],b[50]; for(int i=0;i<50;i++){ a[i]=b[i]=' '; } int correct=0,wrong=0; fgets(a,99,f); fgets(b,99,fp); while(!feof(f)&&!feof(fp)){ if(strcmp(a,b)==0) correct++; else wrong++; fgets(a,99,f); fgets(b,99,fp); } fy=fopen("Grade.txt","w"); fprintf(fy,"Correct:%d\nWrong:%d",correct,wrong); fclose(f); fclose(fp); fclose(fy); }
主函数
int main(){ FILE *f,*fp; int n,r,i,j; int num[30]; char ch[30]; srand(time(0)); f=fopen("Exercises.txt","r+"); printf("1.生成题目和答案 2.检查答案对错:"); scanf("%d",&j); switch(j){ case 1:{ printf("输入生成四则运算的数目:"); scanf("%d",&n); printf("输入数值的最大值:"); scanf("%d",&r); CreateNum(n,r,f); for(i=0;i<n;i++){ getIntandChar(f,num,ch); ChangeFraction(num,ch); turnRPNandCalculate(num,ch); } break; } case 2:{ checkAnswer(); break; } } fclose(f); return 0; }
4.测试
编译并运行程序:

得到两个txt文件:

然后使用答案检验功能:

符合预期效果。
5.项目小结:
难点总结:逆波兰算法的实现是遇到的困难之一,为了攻克这个难关耗费了不少的时间,算法实现方面还是有待改进,首次编程出现了不少的bug,修复的过程也耗时许多,再之就是基础知识不牢固导致的编程过程困难。还是需要好好复习学习以前学过的内容,本次编程项目的实现主要还是靠着赵玮锋熟练的编程能力才得以完成的。希望共同努力、一起进步、有所收获,以后不再做拖后腿的那个。

浙公网安备 33010602011771号