结对项目:四则运算(C语言)

github地址:https://github.com/nilonger/arithmetic

结对伙伴:杨锐龙+黄海钊

一、项目要求

1.1 题目:实现一个自动生成小学四则运算题目的命令行程序(也可以用图像界面,具有相似功能)。

1.2 说明:

  • 真分数:1/2, 1/3, 2/3, 1/4, 1’1/2, …。

  • 自然数:0, 1, 2, …。

  • 运算符:+, −, ×, ÷。

  • 括号:(, )。

  • 等号:=。

  • 分隔符:空格(用于四则运算符和等号前后)。

  • 算术表达式:

    e = n | e1 + e2 | e1 − e2 | e1 × e2 | e1 ÷ e2 | (e),

    其中e, e1和e2为表达式,n为自然数或真分数。

  • 四则运算题目:e = ,其中e为算术表达式。

1.3 项目要求

  • (完成) 使用 -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. 四则运算题目1

  2. 四则运算题目2

   ……

 其中真分数在输入输出时采用如下格式,真分数五分之三表示为3/5,真分数二又八分之三表示为2’3/8。

  • 在生成题目的同时,计算出所有题目的答案,并存入执行程序的当前目录下的Answers.txt文件,格式如下:

   答案1

    答案2

  • 真分数的运算如下例所示:1/6 + 1/8 = 7/24。

  • 程序应能支持一万道题目的生成。

  • 程序支持对给定的题目文件和答案文件,判定答案中的对错并进行数量统计,输入参数如下:

  Myapp.exe -e .txt -a .txt 统计结果输出到文件Grade.txt,格式如下:

    Correct: 5 (1, 3, 5, 7, 9)

    Wrong: 5 (2, 4, 6, 8, 10)

  其中“:”后面的数字5表示对/错的题目的数量,括号内的是对/错题目的编号。为简单起见,假设输入的题目都是按照顺序编号的符合规范的题目。


 二、PSP表

 

PSP2.1

Personal Software Process Stages

预估耗时(分钟)

实际耗时(分钟)

Planning

计划

100

 200

· Estimate

· 估计这个任务需要多少时间

60

 70

Development

开发

6*24*60

 5*24*60

· Analysis

· 需求分析 (包括学习新技术)

60

 50

· Design Spec

· 生成设计文档

20

 20

· Design Review

· 设计复审 (和同事审核设计文档)

30

 30

· Coding Standard

· 代码规范 (为目前的开发制定合适的规范)

20

20

· Design

· 具体设计

100

 150

· Coding

· 具体编码

4*24*60

4*24*60

· Code Review

· 代码复审

100

 150

· Test

· 测试(自我测试,修改代码,提交修改)

40

60

Reporting

报告

20

20

· Test Report

· 测试报告

10

 30

· Size Measurement

· 计算工作量

10

 30

· Postmortem & Process Improvement Plan

· 事后总结, 并提出过程改进计划

30

 30

合计

 

6*24*60

5*24*60

三、解题思路

1、先实现生成式子的功能。

(1)先写好生成操作数和包含1~3个操作符的式子的四个函数,再通过一个函数一起调用,随机生成带括号的式子

(2)除号和乘号÷、×先用*、#代替

(3)C语言里面的除号和乘号÷、×不能直接从文件读出来,所以就放弃了从文件里读出来计算的想法,改为生成的时候,把式子放进数组再来计算,计算不小于0,才把数组存进文件,存进文件的时候把*、#这两个符号替换成÷、×

2、计算功能。

(1)只定义了一个分数结构体,因为想到计算的时候 整数也可以转化为分数,如2可以化为2/1

(2)定义两个栈,手撸栈的各个操作(C语言没办法),一个栈用来压入分数,一个用来压入操作符

3、比较题目文件,判断答案文件中的对错。

(1)这个可以说是取巧了一下,因为文件的生成,帮随着答案的生成,所以比较的是 标准答案文件 和 新答案文件

(2)原本是想从文件里面再读出式子拿来计算,但是写进文件后有了符号÷、×,但是那两个符号÷、×读出来会出错,因为他们不是普通字符型数据的长度。

4、主函数。

用输入参数的方式在cmd运行,很好的分开了生成式子(同时生成答案)和比较答案这两个步骤

四、设计实现过程

 

 

五、代码说明

头文件和相关结构体和栈的定义:

#include<stdio.h>
#include<stdlib.h>
#include <time.h>
#include<string.h>
#include<io.h>
#include<math.h>
#define MINSIZE 256
#define MAXSIZE 1024
#define OK 1
#define ERROR 0


int r,n;   //生成数的最大值 和 式子数量 
typedef int Status;

char* oneOperator();
char* creatOperator();
char* threeOperator();
char* twoOperator();
char *creatFormula(int y);
char* creatSnum();
Status Answer_Sq(char formula[],int y);

typedef struct StackNode{    //分数 栈结点
    int fm;    //分母
    int fz;    //分子
    struct StackNode *next;
}node1;

typedef struct Stackop {  //运算符 栈结点

    int data;            
    struct Stackop *next;
}node2;

typedef struct stack1  { //分数栈

    node1 *top;
    int length;
}StackSq1;

typedef struct stack2  {  //运算符栈

    node2 *top;
    int length;
}StackSq2;

栈的相关操作:

Status InitStack_Sq1(StackSq1 &S){   //初始化 运算数 的栈 

    S.top=NULL;
    S.length=0;
    return OK; 
}

Status InitStack_Sq2(StackSq2 &S){    //初始化 运算符 的栈 

    S.top=NULL;
    S.length=0;
    return OK;
}

Status StackEmpty_Sq(StackSq2 S){  // 对  运算符 判空,若为空则返回TURE,否则返回FALSE 
    if(S.length==0) return OK;
    else return ERROR;
}

Status Push_Sq1(StackSq1 &S,int fenzi,int fenmu){   //分数 进栈 
    node1 *p;
    p=(node1 *)malloc(sizeof (node1));
    p->fm=fenmu;
    p->fz=fenzi;
    p->next=S.top;
    S.top=p;
    S.length++;
    return OK; 
} 

Status Push_Sq2(StackSq2 &S,int e){   //运算符 进栈 
    node2 *p;
    p=(node2 *)malloc(sizeof (node2));
    p->data=e;
    p->next=S.top;
    S.top=p;
    S.length++;
    return OK; 
} 

node1 Pop_Sq1(StackSq1 &S){  //记得类型是 node1 
    node1 A;
    node1 *p=S.top;
    A.fm=p->fm;
    A.fz=p->fz;
    S.top=p->next;
    free(p);
    S.length--;
    return A;
} 

Status Pop_Sq2(StackSq2 &S){   //  运算符栈顶  出栈 
    int e;
    node2 *p=S.top;
    e=p->data;
    S.top=p->next;
    free(p);
    S.length--;
    return e;
} 

Status Get_Top(StackSq2 S){     //取  运算符 栈顶元素 (不出栈) 
    if(S.top==NULL)return ERROR;
    return S.top->data;
}

生成式子的相关函数:

char* creatOperator() //生成运算符
{
    srand((unsigned)time(NULL) + rand());
    char* c = new char[2];
    int a = rand() % 4;
    //int a = 1;
    switch (a)
    {
    case 1:  strcpy(c, "+");  break;
    case 2: strcpy(c, "-"); break;
    case 3: strcpy(c, "*"); break;
    case 0:  strcpy(c, "#"); break;
    default:
        break;
    }
    return c;
}

char* creatSnum()  //生成运算数 
{
    srand((unsigned)time(NULL) + rand());
    char* string = NULL;
    char string1[MAXSIZE] = {};
    string = string1;
    char c[MAXSIZE] = {};
    int tag = rand() % 2;
    int num1=0,num2=0;
    if (tag == 0)
    {
        num1 = rand() % (r)+1;    //不要(size+1),保证整数不为  0 
        sprintf(c, "%d", num1);
        strcat(string1, c);
        
    }
    else
    {
        num1 = rand() %(r-1) ;   //最大为 m-2,方便后面算法 
        
        if (num1 != 0)
        {
            sprintf(c, "%d", num1);
            strcat(string1, c);
            strcat(string1, "/");
        }
        while (num2 == 0 || num2 <= num1)
           {
              num2 = rand() % r;    //最大为 m-1 
           }
        sprintf(c, "%d", num2);
        strcat(string1, c);

    }
    return string;
}

char* oneOperator()  //一个操作符的式子
{
    srand((unsigned)time(NULL) + rand());
    char string[MINSIZE] = {};
    char* posture = string;
    char c[MINSIZE] = {};        
    strcpy(c, creatSnum());
    strcat(string, c);
    
    strcpy(c,creatOperator());
    strcat(string, c);
    //printf("%s\n", string);

    strcpy(c, creatSnum());
    strcat(string, c);
    //printf("%s\n", string);
    return posture;
}

char* twoOperator() //两个操作符得式子
{
    srand((unsigned)time(NULL) + rand());
    int flag = 0;
    int tag=0;
    char string[128] = {};
    char* posture = string;
    char c[MAXSIZE] = {};

    flag = rand() % 2;
    if (flag == 0)
    {
        strcpy(c, "(");
        strcat(string, c);
        tag = 1;
    }

    strcpy(c, creatSnum());
    strcat(string, c);

    strcpy(c,creatOperator());
    strcat(string, c);

    strcpy(c, creatSnum());
    strcat(string, c);

    flag = rand() % 2;
    if (flag == 0)
    {
        if (tag == 1)
        {
            strcpy(c, ")");
            strcat(string, c);
        }
        tag = 0;
    }

    strcpy(c,creatOperator());
    strcat(string, c);

    strcpy(c, creatSnum());
    strcat(string, c);

    if (tag == 1)
    {
        strcpy(c, ")");
        strcat(string, c);
    }

    //printf("%s\n", string);
    return posture;
}


char* threeOperator()  //三个操作符得式子
{
    srand((unsigned)time(NULL) + rand());
    char string[128] = {};
    char* posture = string;
    char c[MAXSIZE] = {};

    strcpy(c, creatSnum());
    strcat(string, c);

    strcpy(c,creatOperator());
    strcat(string, c);

    strcpy(c, creatSnum());
    strcat(string, c);

    strcpy(c,creatOperator());
    strcat(string, c);

    strcpy(c, creatSnum());
    strcat(string, c);

    strcpy(c,creatOperator());
    strcat(string, c);

    strcpy(c, creatSnum());
    strcat(string, c);

    return posture;
}

char pan[5]={"-1"};
char *mp=pan;
char duan[5]={"1"};
char *np=duan;

char *creatFormula(int y)   //生成式子
{    
     srand((unsigned)time(NULL) + rand());
//    char op[3];
    char string[MAXSIZE] = {};
    char* posture = string;
    int a = rand() % 3;    //生成的随机数,即运算符个数
    //printf("%d\n", a);

    FILE *fp;
    fp=fopen("test.txt","a");
    int i=0;
    
    char divi[5]={"÷"};  //用于后面存进文件 
    char mult[5]={"×"};
    
    switch (a)
    {
    case 0:strcpy(string, oneOperator());         
//            printf("%s\n",posture);   //和文件作比较   
            break;
    
    case 1:strcpy(string, twoOperator());
//            printf("%s\n",posture);
            break;
            
    case 2:strcpy(string, threeOperator());
//            printf("%s\n",posture); 
            break;
             
    default:break;
    }
    if(Answer_Sq(posture,y)<0)    //计算的时候用  数组 算,打印则要转化一下 
    {
        fclose(fp);
        return mp;   // 因为是 char 型函数,不能直接返回-1 
    }
    else {
//        fprintf(fp,"%d.%s = \n",y,posture);
        fprintf(fp,"%d.",y);
        while(string[i]!='\0')
            {
                if(string[i]=='#') 
                {
                    fprintf(fp,"%s",divi);
                    i++;
                    continue;
                }
                else if(string[i]=='*')
                {
                    fprintf(fp,"%s",mult);
                    i++;
                    continue;
                }
                fprintf(fp,"%c",string[i]);
                i++;
            }
            fprintf(fp,"=%c",'\n');
        fclose(fp);
        return np;
    }
//    fclose(fp);
//    return posture;
     
}

优先级判断,中间值计算,化简,生成答案文件等函数:

int Priority(char op)    //判断操作符优先级
{
    switch (op)
    {
        case '(':
            return 4;
        case '/':
            return 3;    
        case '*':
        case '#':    //除号 
            return 2;
        case '+':
        case '-':
            return 1;
        default :
            return 0;
    }
}

int gcd(int x,int y)//辗转相除法 ,两整数的最大公约数 
{
    if(y==0) return x;
    return gcd(y,x%y);
} 

node1 CalculatorSq(node1 a,node1 b,int c){  // 两个数的计算 

    node1 d;
    switch(c)
    {
        case'+':
            d.fz=b.fz*a.fm + b.fm*a.fz;
            d.fm=a.fm*b.fm;
            break;
        case'-':
            d.fz=b.fz*a.fm - b.fm*a.fz;
            d.fm=a.fm*b.fm;
            break;
        case'*':
            d.fz=b.fz*a.fz;
            d.fm=a.fm*b.fm;
            break;
        case'#':
        case'/':
             d.fz=b.fz*a.fm;
            d.fm=b.fm*a.fz;
            break;

        default:break;
    }
    return d; 
}

void Simpli_Fenshu(int z,int m,char strings[]) {        //分数化简(将原始分数化为  真分数、带分数、整数)
    int a,b,c;       
    char h[20]={};
    
    if(z%m==0)
    {
        a=z/m;
        itoa(a,strings,10);      //把 a转化为字符,存在strings[]中 
    // printf("%s\n",strings);   //可以直接在 exe窗口 看答案
    }
    else if(z>m) 
    {
        a=z/m;
        b=z-a*m;
        c=gcd(b,m);    //求新的分子分母的  最大公因数 
        itoa(a,strings,10);
        strcat(strings,"'");
        sprintf(h,"%d",b/c);
        strcat(strings,h);
        strcat(strings,"/");
        sprintf(h,"%d",m/c);
        strcat(strings,h);
    //    printf("%s\n",strings);
    }
    else if(z<m)
    {
        c=gcd(z,m);
        a=z/c;
        b=m/c;
        itoa(a,strings,10);
        strcat(strings,"/");
        itoa(b,h,10);
        strcat(strings,h);
    //    printf("%s\n",strings);
    }
}

Status Answer_Sq(char formula[],int y){    //计算答案 ,formula[] 为传进来的式子 
    StackSq1 S_num;  //分数栈 
    StackSq2 S_ope;   //运算符 栈 
    
    int i=0,tmp=0;
    
    node1 A,B,C;   //结点        
    int D;        //运算符 
    
    FILE *fp;
    fp=fopen("answer.txt","a");
    if(InitStack_Sq1(S_num)!=OK || InitStack_Sq2(S_ope)!=OK)
    {
        printf("初始化栈失败");
        exit(1); 
    }
    
    while(formula[i]!='\0' || StackEmpty_Sq(S_ope)!=OK)
    {
        if(formula[i]>='0'&&formula[i]<='9')    //判断数字
        {
            tmp=tmp*10+formula[i]-'0';     //后面还是数字,继续 
            i++;
            if(formula[i]>'9' || formula[i]<'0')                 //判断 后一个 是否 字符 
             {
                 Push_Sq1(S_num,tmp,1);   //把整数化为 分数 进栈
                tmp=0;      //清零,用于记录下一个  数  
             } 
        } 
        
        else   //运算符
        {                //等级高的                                       栈顶是左括号',str[i]不是右括号                运算符栈为空 
            if((Priority(formula[i]) > Priority(Get_Top(S_ope))) || (Get_Top(S_ope)=='(' && formula[i]!=')') || (StackEmpty_Sq(S_ope)==OK))
            {
                Push_Sq2(S_ope,formula[i]);  //运算符 进栈 
                i++;
                continue;
            }
            
            if(Get_Top(S_ope)=='(' && formula[i]==')') 
            {
                Pop_Sq2(S_ope);   //栈顶 括号 出栈,不计算 
                i++;
                continue; 
            }
//                    新的运算符优先级 比  运算符栈顶的 低                     数字空了,运算符还没空                新的运算符是右括号 
            if((Priority(formula[i]) <= Priority(Get_Top(S_ope))) || (formula[i]=='\0'&&StackEmpty_Sq(S_ope)!=OK) || formula[i]==')')
            {
                A=Pop_Sq1(S_num);   //出栈 两个数 
                B=Pop_Sq1(S_num);
                D=Pop_Sq2(S_ope);   //出运算符 
                            
//                printf("aaa%d/%d\n",A.fz,A.fm);    //这些注释都是为了在exe窗口显示 计算过程 
//                printf("bbb%d/%d\n",B.fz,B.fm);
//                printf("ccc%c\n",D);
                
                C=CalculatorSq(A,B,D);     //计算中间值 (注意这里!!!)
            
//                printf("daan%d/%d\n",C.fz,C.fm);
                
                Push_Sq1(S_num,C.fz,C.fm);   //中间值为正值,入栈 
                continue;
            } 
        } 
    }
    if(C.fz<0)
    {    
        fclose(fp);
        return -1;   //  负数就返回 -1
    }
    
    char daan[20]={};   //用来存答案 
    Simpli_Fenshu(C.fz,C.fm,daan);  //返回简化后的答案 
    fprintf(fp,"%d.%s\n",y,daan);
    fclose(fp);
    return 1;    
}

判断答案文件对错函数:

void CheckAnswer(char eflie1[],char afile2[]){
    FILE *p1,*p2,*p3;

    p1=fopen("answer.txt","r");
    p2=fopen(afile2,"r");       //传进来的文件名会变 
    p3=fopen("Grade.txt","w");
    
    char answer1[20]={};  //放标准答案的数组 
    char answer2[20]={};  //放新答案文件 fgets出来的答案
     
    int y=1;       //第一条式子 
    int c=0,w=0;   //用于计算对和错的总题数 
    int correct_d[10000]={};   //用于储存  对的和错的题号
    int wrong_d[10000]={};
    
    while(fgets(answer1,20,p1)!=NULL && fgets(answer2,20,p2)!=NULL)
    {    
        //不要fgets  10个字符 
    
//        printf("%s",answer1);      
//        printf("222%s",answer2);
        
        if(strcmp(answer1,answer2)==0)  //比较前n个字节的大小 
        {
            correct_d[c++]=y;
            y++; 
        }
        else if(strcmp(answer1,answer2)!=0)
        {
            wrong_d[w++]=y;
            y++;
        }
    }
 
    fprintf(p3,"Correct: %d (",c);
    for(int i=0;i<c;i++)
    {
        fprintf(p3,"%d",correct_d[i]);
        if(i!=c-1) fprintf(p3,",");
    }
    fprintf(p3,")\n");
     
    fprintf(p3,"Wrong: %d (",w);
    for(int i=0;i<w;i++)
    {
        fprintf(p3,"%d",wrong_d[i]);
        if(i!=w-1) fprintf(p3,",");
    }
    fprintf(p3,")");
    fclose(p1);
    fclose(p2);
    fclose(p3);        
} 

带参数的主函数:

int main(int argc,char *argv[]){
    char *line;
    int y=1;
    if(argc<3)   //参数没有 4个 
    {
        printf("你输错了或 去cmd运行!!!\n");
        return 0;
    }
    if(strcmp(argv[1],"-n")==0 && strcmp(argv[3],"-r")==0)
    {
        n=atoi(argv[2]); //字符串变为整数 
        r=atoi(argv[4]);
        
        srand((unsigned)time(NULL)+rand());   //加个种子,随机数不同 
        while(n>0)
        {   
            
            line=creatFormula(y);    
            if(atoi(line)<0)continue;     
            y++;
            n--;    
        }    
    } 
    else if(!strcmp(argv[1],"-e") && !strcmp(argv[3],"-a"))
    {
        CheckAnswer(argv[2],argv[4]);     
    }
    return 0;
}

六、测试运行

1、测试生成

 2、判断答案对错:

 

10000道题目:

 

 

 

 

七、项目小结

杨锐龙:

1、对于这次结对项目,可以说的是我是比较懒的,要不是海钊在项目发出来两三天后开始一直催着我,可能后来项目的完成会变得比较紧张,在完成项目的过程中,我们的分歧感觉很大。

2、我们没有规划好谁完成什么功能,可以说都是独立做自己的内容,然后看谁实现的功能更多更好然后决定用谁的方法,这当中也出现了不少的矛盾,因为我两都是独立写的,沟通多但是效果不好,导致有时候他问我怎么办的时候我也没有好的思路给他,或者说根本就不知道他要干嘛(这可能是这次结对项目最遗憾的地方了)。

3、虽说如此,但是也是有好的一点地方,比如在生成式子的那个阶段,他的方法用得比我更好,以至于到后来我完成自己项目的时候,还是引用了他的方法,给我带来了很大的方便,虽然沟通效果不好,但是我们也是有经常分享自己的代码成果,从中也可以get到一些不同于我的思路,比如主函数我开始是没用用参数的,在后面生成和对比答案的时候带来了一定的麻烦,但是想到了他给我的代码中出现过的带参数的主函数,也就顺便解决了出现的问题。

4、这次的结对项目可以遗憾地说是个人项目,是我们(或者我)没有get到结对项目的点上,只是多了一个伙伴相互分享代码,但是值得高兴的是我们都用自己的想法完成了项目,只是花费很多的时间。

 黄海钊:

这是我第一次跟别人合作做项目,我觉得合作最大得好处就是可以共享知识,但是也带来了一个很大得问题,就是矛盾。我们有进行讨论,但是在讨论过程中意见不一,各有各的想法,导致讨论没能有太大作用。最终在共享思路的情况下各自完成代码,取其优而选着。虽说完成的不是很好,但也是经历了一个不错的过程。

 

posted @ 2020-04-12 20:03  牧杨  阅读(540)  评论(0编辑  收藏  举报