zoukankan      html  css  js  c++  java
  • 结对项目:四则运算(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
    ", string);
    
        strcpy(c, creatSnum());
        strcat(string, c);
        //printf("%s
    ", 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
    ", 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
    ", 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
    ",posture);   //和文件作比较   
                break;
        
        case 1:strcpy(string, twoOperator());
    //            printf("%s
    ",posture);
                break;
                
        case 2:strcpy(string, threeOperator());
    //            printf("%s
    ",posture); 
                break;
                 
        default:break;
        }
        if(Answer_Sq(posture,y)<0)    //计算的时候用  数组 算,打印则要转化一下 
        {
            fclose(fp);
            return mp;   // 因为是 char 型函数,不能直接返回-1 
        }
        else {
    //        fprintf(fp,"%d.%s = 
    ",y,posture);
            fprintf(fp,"%d.",y);
            while(string[i]!='')
                {
                    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",'
    ');
            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
    ",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
    ",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
    ",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]!='' || 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]==''&&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
    ",A.fz,A.fm);    //这些注释都是为了在exe窗口显示 计算过程 
    //                printf("bbb%d/%d
    ",B.fz,B.fm);
    //                printf("ccc%c
    ",D);
                    
                    C=CalculatorSq(A,B,D);     //计算中间值 (注意这里!!!)
                
    //                printf("daan%d/%d
    ",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
    ",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,")
    ");
         
        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运行!!!
    ");
            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到结对项目的点上,只是多了一个伙伴相互分享代码,但是值得高兴的是我们都用自己的想法完成了项目,只是花费很多的时间。

     黄海钊:

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

  • 相关阅读:
    elasticsearch painless脚本评分
    elasticsearch relevance score相关性评分的计算
    java 多线程间通信(二)
    java 多线程间通信(一)
    java CountDownLatch、CyclicBarrier和 Semaphore用法
    java 深入剖析ThreadLocal
    java中String、StringBuffer、StringBuilder的区别
    Leetcode: LRU Cache
    Leetcode: Anagrams(颠倒字母而成的字)
    Leetcode: First Missing Positive
  • 原文地址:https://www.cnblogs.com/nilonger/p/12685950.html
Copyright © 2011-2022 走看看