四则运算表达式生成器(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. 这次的结对项目,可以说大部分是同伴完成的,我自身却一直停留在了真分数的困扰中,如果没有他独挑大梁,我怕是完成不了这次的项目。

原文地址:https://www.cnblogs.com/wen328328/p/12700268.html

时间: 2024-11-07 00:59:50

四则运算表达式生成器(C语言)的相关文章

计算四则运算表达式(Java语言实现)

计算四则运算表达式主要是分两步运算  第一步是把 中缀表达式 转换成 后缀表达式.参考大一下期学的 <数据结构与算法分析--C语言描述>3.3.3 教材,完成下列代码: static String mid_to_suf(String str){ Stack<Character> s = new Stack<Character>(); String suf = new String(); HashMap<Character, Integer> map = ne

结对编程1 —— 基于GUI和Swing的四则运算题目生成器

结对编程1 -- 基于GUI和Swing的四则运算题目生成器 合作伙伴 201421123089 周迪 201421123069 黄睿 代码地址:https://git.coding.net/H1159650478/sizeyunsuanjiaqiang.git 题目描述 我们在个人作业1中,用各种语言实现了一个命令行的四则运算小程序.进一步,本次要求把这个程序做成GUI(可以是Windows PC 上的,也可以是Mac.Linux,web,手机上的),成为一个有基本功能.一定价值的程序.在下面

四则运算表达式求值 OpenJ_Bailian - 4132

四则运算表达式求值 OpenJ_Bailian - 4132 题意:设计一个计算器,实现+-*/以及()的表达式运算求值. 栈的应用,这学期学数据结构,手写了栈练一下~ 1 #include <bits/stdc++.h> 2 using namespace std; 3 const int maxn=10010; //最大表达式长度 4 5 template <typename T> 6 class Stack; 7 8 template <typename T> 9

四则运算表达式求值の各种心碎

实验三---四则运算表达式求值 一.基本要求: ( 1 ) 利用二叉树后序遍历来实现表达式的转换,同时可以使用实验三的结果来求解后缀表达式的值. ( 2) 输入输出格式: 输入格式:在字符界面上输入一个中缀表达式,回车表示结束. 请输入表达式: 输入一个中缀表达式 输出格式:如果该中缀表达式正确,那么在字符界面上输出其后缀表达式,其中后 缀表达式中两相邻操作数之间利用空格隔开:如果不正确,在字符界面上输出表达式错误提示. 逆波兰表达式为: 输出逆波兰表达式 运算结果为:输出运算后的结果 测试数据

介绍一款原创的四则运算算式生成器:CalculateIt2

家里小朋友读一年级了,最近每天都有一些10以内的加减法口算练习,作为程序员爸爸,自然也是想办法能够偷懒,让电脑出题,给小朋友做些练习.于是,自己在业余时间开发了一个四则运算算式生成器,名为:CalculateIt2.项目是开源的,源码地址是:https://github.com/daxnet/CalculateIt2.也可以在标准的.NET Framework 4.6.1的项目中,通过nuget来引用这个类库,nuget ID是:CalculateIt2.Engine.为什么名字后面有一个"2&

数据结构(7)----栈与队列之栈的应用四则运算表达式求值

栈与队列之栈的应用四则运算表达式求值 栈在四则运算表达式求值的应用为逆波兰表达式(后缀表达式) 普通算式(中缀表达式):9 + (3 - 1) * 3 + 10 / 2     ---(1) 逆波兰表达式(后缀表达式):9 3 1 - 3 * + 10 2 /         ---(2) 1:逆波兰表达式的计算规则 从左到右遍历表达式的每个数字和符号,遇到数字就进栈,遇到符号,就将处于栈顶的两个数字出栈,进行运算,再把运算结果进栈,一直到最终获得结果.接下来我们以(2)式为例:

四则运算表达式总结

四则运算表达式总结 这两周完成了第一个个人项目--四则运算表达式.在整个开发的过程中我有一些收获也发现了自身的一些问题.下面就分条简述一下. 完成情况 1.实现随机生成任意长度的四则表达式(+.-.*./.括号). 2.实现四则表达式的计算(包括分数). 3.实现接收用户对某四则运算表达式计算的结果并判断正确性. 4.实现结合1.2.3功能的MFC应用程序 总结 整个的设计与实现过程中,收获颇多.首先就是我发现在实现之前能够有一个比较良好的设计思路以及程序流程的重要性(最好是能够记录下来而不是存

四则运算表达式解析和求值(支持括号、小数、小数正负号)

需求分析 四则运算表达式的解析和求值从概念上看是一个逻辑很清晰的过程. 遵循了左结合.优先级差异和括号可以提升优先级这三条规则. 实现思路 实际上只要遍历表达式,将小数和运算符分离到两个序列容器中,在遍历的过程中,考虑左结合和括号对优先级的影响,将相关信息和运算符记录在一起,然后在遍历结束后,按照优先顺序从高到底,从小数序列中拉取两个元素(可能是中间结果的二叉树,实际上小数是一种特例,是左右子树为空的解析完成的二叉树),并且把运算符的两棵子树指向两个元素,把这棵新树放回到小数容器中.不断重复这个

Util应用程序框架公共操作类(十一):表达式生成器

本篇介绍的表达式生成器,用于动态创建表达式. 在Util项目Lambdas目录中,添加ExpressionBuilder,代码如下. using System; using System.Linq.Expressions; namespace Util.Lambdas { /// <summary> /// 表达式生成器 /// </summary> public class ExpressionBuilder<TEntity> { /// <summary>