四则运算表达式生成器(C语言)

结对项目:四则运算表达式生成器(C语言)

GitHub:https://github.com/peter-ye-code/Question-Builder

合作者:叶学涛(3118005024)

              温德华(3118005021)

一、需求

 

四则运算表达式生成器的全部功能:

    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,它们之间不能通过有限次交换变成同一个题目。 

    7. 生成的题目存入执行程序的当前目录下的Exercises.txt文件。 

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

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

    10. 程序支持对给定的题目文件和答案文件,判定答案中的对错并进行数量统计,输入参数如下:Myapp.exe -e .txt -a .txt,统计结果输出到文件Grade.txt,格式如下:
      Correct: 5 (1, 3, 5, 7, 9)
      Wrong: 5 (2, 4, 6, 8, 10)
      其中“:”后面的数字5表示对/错的题目的数量,括号内的是对/错题目的编号。为简单起见,假设输入的题目都是按照顺序编号的符合规范的题目。

未完成的功能:(4)功能中的真分数功能未能实现

二、PSP表格

PSP2.1

Personal Software Process Stages

预估耗时(分钟)

实际耗时(分钟)

Planning

计划

30

40

· Estimate

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

30

40

Development

开发

1150

1200

· Analysis

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

200

250

· Design Spec

· 生成设计文档

30

30

· Design Review

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

10

15

· Coding Standard

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

20

30

· Design

· 具体设计

500

435

· Code Review

· 代码复审

120

90

· Test

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

300

350

Reporting

报告

60

70

· Test Report

· 测试报告

20

25

· Size Measurement

· 计算工作量

10

10

· Postmortem & Process Improvement Plan

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

30

35

合计

 

1240

1310

三、设计实现过程

1、设计思路

采用随机法生成运算表达式,运算顺序则采用中缀表达式转后缀表达式,结果及运算数则使用栈来进行存取。

2、模块函数关系

四、主要代码

生成表达式

int CreatQuestion (int m,int digital_index,int *answer) 
{
    char str[250] = {'0'};  //定义一个字符数组存放算术表达式
    int x, num, ran_num, j = 0;  
    num = random_number( 3 );//生成随机运算符数目 
    
    //循环生成算术表达式
    for ( int k = 0; k < num; k++ ) 
    {
        ran_num = random_number ( m );  //生成一个参与表达式的数值,m是数值范围 (1~10) 
        x = 1;
        while ( ran_num / x )
        {
            x *= 10;
        }
        x /= 10;//x=10,x=1

        while ( x )//x=10执行两次 
        {
            str[j++] = ran_num / x + '0';//将数字型转换为字符串型 
            ran_num = ran_num % x;
            x /= 10;
        }

        str[j++] = random_symbol();  //随机生成一个运算符

    }

    //生成一个随机结尾数值
    ran_num =  random_number( m );  
    x = 1;
    while ( ran_num / x )
    {
        x *= 10;
    }
    x /= 10;

    while ( x )
    {
        str[j++] = ran_num / x + '0';
        ran_num = ran_num % x;
        x /= 10;
    }
    
    int result = Answer(str,digital_index);
    *answer = result;
    if(result>=0){
        WriteQuestion(str,digital_index);
        WriteAnswer(digital_index,result);
    }
}

计算答案

//生成答案模块
int Answer(char *str,int digital_index) 
{
    char* p =str;
    
    int num[100] = {0};//数字数组
    char symbol[100] = {0};//运算符数组 
    int index = 0;    //索引  
    
    char stack[100] = {0};//运算符栈 
    int stacki[100] = {0};//结果栈 
    int top = -1;//栈的索引 -1表示栈为空 
    
    int temp = 0;//正在拼写的数字 
    int flag = 0;//表示当前是否正在拼写数字
    
    //中缀表达式转化为逆波兰表达式
    //最后的结果就是得到两个数组,分别是num和symbol 
    while(1){
        if(*p>='0' && *p<='9')//读到了一个数字 
        {    flag = 1;
            temp *= 10;//第一个为数字时temp=0,第二个为数字时才乘以10 
            temp += *p - '0';//0的ASCII值为48
        }
        else//读到了一个符号,或字符串已经结束了
        {
            if(flag)//拼写完1个数字先将数字输出 
            {
               num[index] = temp;
               symbol[index] = '!';
               index++;
               flag = temp =0;//重新置0 
            }
            if(!*p)//如果字符串已经结束 
            {
                //最终,将栈元素全部出栈
                while(top>=0) symbol[index++]=stack[top--];
                break;//跳出循环 
            }
            else// 字符串还未结束 
            {
                 if(top == -1||*p == '(') //如果栈空时,或符号为左括号          
                     stack[++top] = *p;//入栈 
                 else if(*p == ')')//如果是右括号,出栈到左括号
                 {
                     while(top>=0&&stack[top]!='(')//如果不为左括号。出栈 
                     {                                             
                        symbol[index++]=stack[top--];
                    } 
                    --top;//如果为左括号,--top 
                 }         
                else if(*p == '*'||*p == '/')//如果新符号是乘除
                 {
                     while(top>=0&&(stack[top]=='*'||stack[top]=='/'))//乘除出栈
                     {                                             
                        symbol[index++]=stack[top--];
                    } 
                    stack[++top] = *p;//新符号入栈
                 }
                 else //如果新运算符是加减
                 {
                     while(top>=0&&stack[top]!='(')//四则运算出栈
                     {                                             
                        symbol[index++]=stack[top--];
                    } 
                    stack[++top] = *p;//新符号入栈
                 }
            }
        }
        p++;
    }
    
    //逆波兰表达式求解
    top =-1;
    int temp1 = 0;
    int temp2 = 0;
    for(int i =0;i<index;i++){
        if(symbol[i]=='!'){//这时的i索引表示的是数字,数字入栈stacki 
            stacki[++top] = num[i];    
        }
           //符号运算
           else
           {
               temp1 = stacki[top--];
            temp2 = stacki[top--];
            switch (symbol[i])
            {
            case '+':
                stacki[++top] = temp2 + temp1;//将结果入栈 
                break;
            case '-':
                stacki[++top] = temp2 - temp1;//后面减去前面 
                break;
            case '*':
                stacki[++top] = temp2 * temp1;
                break;
            case '/':
                stacki[++top] = temp2 / temp1;
                break;
            }
        }
    }
    //最终结果是stacki[0]
    int result = stacki[0];
//    printf("%s=%d\n",str,result);    
//    WriteAnswer(digital_index,result);
    return result; 
}

生成答案文件

void WriteQuestion(char *str,int digital_index){
    int k=0;
    FILE *fp;
       fp=fopen("Exercises.txt","a");//若文件不存在则建立该文件,这里不能用w,要用a 
       fprintf(fp,"%d:",digital_index);
       while(str[k]!=NULL){
           if(str[k] == '+' ||str[k] == '-' ||str[k] == '*' ||str[k] == '/' || str[k] == '='){
                fprintf(fp," %c ",str[k]);
        }else fprintf(fp,"%c",str[k]);
        k++;
       }
    fprintf(fp," =\n");
    fclose(fp);
} 

生成题目文件

void WriteAnswer(int digital_index,int result){
    FILE *fp;
       fp=fopen("Answers.txt","a");//若文件不存在则建立该文件,这里不能用w,要用a 
       fprintf(fp,"%d:",digital_index);
       fprintf(fp,"%d\n",result);
       fclose(fp);
}

检查答案并且生成报告文件

void CheckAnswer(char exercisefile[],char answerfile[]){
    FILE *fp1,*fp2,*fp3;

    fp1=fopen("Answers.txt","r");
    fp2=fopen(answerfile,"r");     
    fp3=fopen("Grade.txt","w");
    
    int index=1; //题目序号
    
    char correct_answer[30]={};  //存放正确答案 
    char answer[30]={};  //存放从答案文件取出的答案
     
    
    int c_num=0,w_num=0;   //用于计算对和错的总题数 
    
    int c_index[10000]={};   //用于储存对的和错的题号
    int w_index[10000]={};
    
    while(fgets(correct_answer,30,fp1)!=NULL && fgets(answer,30,fp2)!=NULL)
    {      
        if(strcmp(correct_answer,answer)==0)  //比较前n个字节的大小 
        {
            c_index[c_num++]=index;
            index++; 
        }
        else if(strcmp(correct_answer,answer)!=0)
        {
            w_index[w_num++]=index;
            index++;
        }
    }
 
    fprintf(fp3,"Correct: %d (",c_num);
    for(int i=0;i<c_num;i++)
    {
        fprintf(fp3,"%d",c_index[i]);
        if(i!=c_num-1) fprintf(fp3,",");
    }
    fprintf(fp3,")\n");
     
    fprintf(fp3,"Wrong: %d (",w_num);
    for(int i=0;i<w_num;i++)
    {
        fprintf(fp3,"%d",w_index[i]);
        if(i!=w_num-1) fprintf(fp3,",");
    }
    fprintf(fp3,")\n");
    fclose(fp1);
    fclose(fp2);
    fclose(fp3);        
}

主函数

int main(int argc,char*argv[]){
    
    if(argc<2){
        printf("you must input argc!");
        return 0;
    }
    srand((int)time(0));//设置rand()产生随机数时的随机数种子
    FILE *fp1,*fp2;
    
    int n,r;
    if(!strcmp(argv[1],"-n") && !strcmp(argv[3],"-r")){//生成题目和答案 
        fp1=fopen("Exercises.txt","w");
        fp2=fopen("Answers.txt","w");
        fclose(fp1);
        fclose(fp2);
        n=atoi(argv[2]);
        r=atoi(argv[4]);
        int answer;
        int digital_index=1;
        while(digital_index<=n){
            CreatQuestion(r,digital_index,&answer);
            if(answer>=0){
            digital_index++;
            }
        }   
    }else if(!strcmp(argv[1],"-e") && !strcmp(argv[3],"-a")){//检查答案 
        CheckAnswer(argv[2],argv[4]);
    }
     
    
    return 0;
}

五、测试运行

1、生成10道题目及答案文件

2、生成1W道题目及答案文件

3、检查文件的生成

 六、经验及总结

  • 叶学涛: 
  1. PSP表的实际花费时间和预估耗时差不多,这次任务的话花在学习新技术的时间上会比较多一些,主要学习的是调场度算法和逆波兰转换式。
  2. 因为前期对需求分析不够,导致真分数这一部分的功能未能实现,这是比较遗憾的一点。
  3. 在进行答案检查这一部分功能的实现,花的时间比较多,本来想的方法是比较复杂的,因此花费了不少时间在思考上,后来换了一种方法,也能达到目标,但代码量明显减少了。
  4. 结对的感受:结对的感受就是能互相发现问题,使开发效率更高,但有一点就是因为在家里的原因,无法准确地沟通,这一点导致了需求分析没有做好,有一部分功能未能实现。
  • 温德华:
  1. 这次的结对项目中,大致了解了中缀表达式转后缀表达式以及对于多个模块函数的嵌套调用,也学会了栈的更深层次的使用。
  2. 真分数方面是困扰着我的难题,知道项目结束仍然没能解决,没能很完全地实现所给功能。
  3. 同时也明白了自身的诸多不足,对于设计整个程序的总体模块分布及大致框架比较模糊,无法将多个函数巧妙地结合起来实现。
  4. 这次的结对项目,可以说大部分是同伴完成的,我自身却一直停留在了真分数的困扰中,如果没有他独挑大梁,我怕是完成不了这次的项目。

猜你喜欢

转载自www.cnblogs.com/wen328328/p/12700268.html