自动生成四则运算题目(C语言)

ε祈祈猫儿з 提交于 2020-04-15 11:01:15

【推荐阅读】微服务还能火多久?>>>

Github项目地址:https://github.com/huihuigo/expgenerator

合作者:马文辉(3118005015)、卢力衔(3118005013)

项目简介

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

2说明:

自然数: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为算术表达式。

3需求:

  1. (完成)使用 -n 参数控制生成题目的个数,例如  Myapp.exe -n 10  将生成10个题目。
  1. (完成)使用 -r 参数控制题目中数值(自然数、真分数和真分数分母)的范围,例如  Myapp.exe -r 10  将生成10以内(不包括10)的四则运算题目。
    该参数可以设置为1或其他自然数。该参数必须给定,否则程序报错并给出帮助信息。
  1. (完成)生成的题目中计算过程不能产生负数,也就是说算术表达式中如果存在形如e1− e2的子表达式,那么e1≥ e2。
  2. (完成)生成的题目中如果存在形如e1÷ e2的子表达式,那么其结果应是真分数。
  3. (完成)每道题目中出现的运算符个数不超过3个。
  4. (完成)程序一次运行生成的题目不能重复,即任何两道题目不能通过有限次交换+和×左右的算术表达式变换为同一道题目。
    例如,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。

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

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

  1. (完成)程序应能支持一万道题目的生成。
  2. (未完成)程序支持对给定的题目文件和答案文件,判定答案中的对错并进行数量统计,输入参数如下: 

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表示对/错的题目的数量,括号内的是对/错题目的编号。为简单起见,假设输入的题目都是按照顺序编号的符合规范的题目。

PSP表格

PSP2.1

Personal Software Process Stages

预估耗时(分钟)

实际耗时(分钟)

Planning

计划

 30

 40

· Estimate

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

 30

 40

Development

开发

 1080

 1100

· Analysis

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

 120

 120

· Design Spec

· 生成设计文档

 60

 70

· Design Review

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

 30

 40

· Coding Standard

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

 30

 30

· Design

· 具体设计

 120

 120

· Coding

· 具体编码

 480

 480

· Code Review

· 代码复审

 120

 120

· Test

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

 120

 120

Reporting

报告

 90

 80

· Test Report

· 测试报告

 30

 30

· Size Measurement

· 计算工作量

 30

 20

· Postmortem & Process Improvement Plan

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

 30

 30

合计

 

1200 

1220 

需求分析及方案介绍

操作数相关的需求分析选取自1.5版本说明:

 

 一些前言

  采用的方案是将一条四则运算表达式转化为一棵相应的二叉树,表达式的运算顺序与二叉树的树形结构相对应,如果要求随机生成表达式,那么对应的表达式树的结构也是随机的。

所以,可以反向操作,随机生成表达式树的树形结构,也对应随机生成了一条表达式

 

 

方案具体介绍(From 卢力衔,马文辉):

方案总体是,先随机生成只有运算符的树形结构,在叶子结点加入操作数结点之后,就生成了一条表达式。

 

  生成一条随机四则运算表达式的完整过程有 6 条步骤。

    1.先生成当前表达式的运算符数量,再随机选择运算符;

    2.将选择的运算符,组合成一个树形结构,也采用随机的方式;

    3.在运算符树(只有运算符的树)的叶子结点上添加操作数(操作数也随机生成,思路就是上图的需求分析);

      4.判断生成的表达式合不合法(主要检测除数是否为0),如果合法则计算出表达式的结果;

      5.检测是否与之前的表达式存在重复关系,即需求6的检测;

        6.进行文件读写。

 

设计实现过程

 整个程序分为4个cpp文件和1个头文件,分别为

Myapp.h           //结构体定义和函数声明

Myapp.cpp(main函数文件)    //main函数中处理命令行参数和程序主逻辑

createExp.cpp          //有关创建一条表达式的函数实现,对应上述方案中的1、2、3点

judgeExp.cpp           //有关判断表达式的合法性和重复性的函数实现,对应上述方案中的4、5点

writeExpAndAnswer.cpp          //有关表达式写入文件的函数实现,对应上述方案中的6点

 

在一个功能模块编码之前,结对的两人先讨论好功能实现的方案,至少给出一个可行的方案,确定好方案之后,再实行编码。

由于这次的代码文件不大,所以就没用使用github进行版本的交接,而是通过微信相互发送文件,附上当前代码版本的改动说明,

同时一次交接后就更新版本号,便于区分不同阶段的代码。这次的结对项目经过了从1.0版本到最后的2.0版本。

下面是开发过程中的一些版本的说明:

       

 

  

具体设计过程:

第一阶段,不考虑重复性,合法性的检测和命令行参数的输入,先生成运算符的树形结构和添加操作数的操作,操作数考虑上减法和除法的要求;同时,完成在控制台中输出打印表达式的中缀表达式的函数实现。

第二阶段,考虑合法性的检测,显式上的除法已经不会出现1/0这样的表达式了,但是还是有可能生成6/(3 - 3)这样的表达式,因此需要加入合法性的检测。在合法性的检测,是通过计算该表达式的运算结果的方式来检测的。

第三阶段,考虑重复性的检测,例如题目中的(1 + 2) + 3与3 + (1 + 2)的重复情况,同时遇到了一种情况,一开始的输出结果都是在控制台中输出表达式,对于1 + (2 + 3)这种表达式,我们认为加法和乘法满足交换律,就省略了后面括号的输出,以至于出现了(1 * 1)/ 1和1 *(1 / 1)两种表达式都输出为1 * 1 / 1的形式,所以对表达式的中缀输出作了调整。

  而且,我们的方案检测重复性是与前面生成的所有表达式进行比较,表达式树比较的函数是递归实现的,这样会出现一种循环+递归的代码结构,简单进行循环递归会导致程序运行速度变慢,这不是能接受的方案。因此,提出了先比较两条表达式的基本信息,先比较运算符数量,运算符是否相同,操作数是否相同,结果是否相同的基本信息,最大程度上减少递归比较的次数,只有在前面的基本信息都相同的情况下,才会触发最后的表达式树递归比较。

第四阶段,实现文件读写和命令行参数的输入,错误处理等,重新检查代码。

 

代码说明

文件中的代码,函数是有顺序的,在函数头的注释中可以找到当前函数的功能,参数和返回值。

//此处为Myapp.h头文件代码
#ifndef MYAPP_H_INCLUDED
#define MYAPP_H_INCLUDED #include <stdio.h> #include <stdlib.h> #include <time.h> #include <string.h> #include <unistd.h> #define TRUE 1 #define FALSE 0 typedef int Status; //操作数结构体定义 typedef struct NumInfo{ //denominator 分母, numerator 分子 int numerator; int denominator; } NumInfo; //表达式树结点定义 typedef struct BiTNode{ union { char opSym; //运算符 NumInfo numForm; //操作数 } value; int flag; //0:value = opSym 1:value = num -1:undefined struct BiTNode *lchild, *rchild; } BiTNode, *BiTree; //表达式基本信息结构体定义 typedef struct { BiTree ExpTree; //表达式树 int symCount; //运算符个数 char* symArr; //运算符数组 NumInfo* opNumArr; //操作数数组 NumInfo result; //表达式结果 } ExpInfo, *ExpInfoPtr; //生成运算符树部分的函数声明 char* chooseSym(int symNum); BiTree createSymTree(int sym, char* symArr); BiTNode* createSymNode(int& symArrIndex, char* symArr); //插入操作数部分的函数声明 void addOpNumInSymTree(int numRange, BiTree symTree, int& flag); void setNumForSubtraction(int &num1_numerator, int &num1_denominator, int &num2_numerator, int &num2_denominator, int numRange); void setNumForDivision(int &num1_numerator, int &num1_denominator, int &num2_numerator, int &num2_denominator, int numRange); void getRandomNum(int &numerator_, int &denominator_, int numRange); BiTNode* createNumNode(int numerator, int denominator); //检查表达式是否合法的函数声明,包含计算表达式的函数 Status isLegalExp(BiTree ExpTree, NumInfo& result); NumInfo getValue(BiTree ExpTree, int&flag, int model); int findGCD(int a, int b); int findLCM(int a, int b); NumInfo simplify(NumInfo numForm); //检测是否为重复的表达式的函数声明 Status isRepetitive(ExpInfoPtr ExpArr, ExpInfo tempExpInfo, int ExpIndex); Status ExpTreeCmp(BiTree T1, BiTree T2); Status symArrCmp(char* arr1, char* arr2, int length); Status opNumArrCmp(NumInfo* arr1, NumInfo* arr2, int length); void addInExpArr(ExpInfoPtr ExpArr, ExpInfo tempExpInfo, int ExpIndex); void getOpNumArr(BiTree ExpTree, NumInfo* OpNumArr, int& OpNumArrIndex); //题目和答案文件读写函数的声明 Status Priority_com(char c1, char c2); void writeExp(BiTree T, FILE* file); void writeAnswer(NumInfo result, FILE* file); #endif // MYAPP_H_INCLUDED

 

下面是Myapp.cpp代码,包含main函数:

  1 #include "Myapp.h"
  2 #include "createExp.cpp"
  3 #include "judgeExp.cpp"
  4 #include "writeExpAndAnswer.cpp"
  5 
  6 Status isNumString(char* str)
  7 {
  8     int i;
  9     for(i = 0; i < strlen(str); i++)
 10         if(str[i] < '0' || str[i] > '9')
 11         return FALSE;
 12     return TRUE;
 13 }
 14 
 15 int main(int argc, char *argv[])
 16 {
 17     int questionCount = 0, numRange = 0; 
 18 
 19     int opt;
 20     while((opt=getopt(argc,argv,"n:r:"))!=-1)
 21     {
 22         switch(opt)
 23         {
 24             case 'n':   if(isNumString(optarg))   questionCount = atoi(optarg);
 25                         break;
 26             case 'r':   if(isNumString(optarg))   numRange = atoi(optarg);
 27                         break;
 28             default:    ;
 29         }
 30     }
 31     if( questionCount == 0 || numRange == 0 )
 32     {
 33         printf("命令行参数输入错误...\n");
 34         printf("输入格式为Myapp.exe -n 题目数(整数) -r 范围值(整数)\n");
 35         exit(1);
 36     }
 37     srand((int)time(0));
 38 
 39 
 40     FILE* questionFile,  *answerFile;
 41     questionFile = fopen("question.txt", "w");
 42     answerFile = fopen("answer.txt", "w");
 43     if(!questionFile || !answerFile)
 44     {
 45         printf("文件打开失败...按任意键退出\n");    getchar();
 46         exit(1);
 47     }
 48 
 49     ExpInfoPtr ExpArr = NULL;
 50     ExpArr = (ExpInfoPtr)malloc( (questionCount+1) * sizeof(ExpInfo));
 51     if(!ExpArr)
 52     {
 53         printf("空间申请失败...按任意键退出\n");    getchar();
 54         exit(1);
 55     }
 56 
 57     int ExpIndex = 1;
 58     int repetiveTime = 0;
 59     int runtimeFlag = TRUE;
 60 
 61     while(ExpIndex <= questionCount)
 62     {
 63         //1、先生成运算符数量,再随机选择运算符
 64         int symCount = rand()%3 +1;   //一条表达式最多选择3个运算符
 65         char* symArr = chooseSym(symCount);
 66         if(!symArr)
 67         {
 68             runtimeFlag = FALSE;
 69             break;
 70         }
 71 
 72         //2、运算符搭成树
 73         BiTree symTree = createSymTree(symCount, symArr);
 74         if(!symTree)
 75         {
 76             runtimeFlag = FALSE;
 77             break;
 78         }
 79 
 80         //3、在运算符树的叶子结点加操作数
 81         int addFlag = TRUE;
 82         addOpNumInSymTree(numRange, symTree, addFlag);
 83         if(addFlag == FALSE)
 84         {
 85             runtimeFlag = FALSE;
 86             break;
 87         }
 88 
 89         //4、判断生成的表达式是否合法,主要检测除数是否为0,并进行文件读写
 90         BiTree ExpTree = symTree;
 91         NumInfo result;
 92 
 93         if( isLegalExp(ExpTree, result) )
 94         {
 95             //5、表达式合法则检查重复性
 96             //5.1   先获取当前表达式的基本信息
 97             ExpInfo tempExpInfo;
 98             tempExpInfo.ExpTree = ExpTree;
 99             tempExpInfo.symCount = symCount;
100             tempExpInfo.symArr = symArr;
101 
102             int opNumCount = symCount+1;
103             NumInfo* opNumArr = (NumInfo*)malloc( opNumCount*sizeof(NumInfo));
104             if(!opNumArr)
105             {
106                 runtimeFlag = FALSE;
107                 break;
108             }
109 
110             int opNumArrIndex = 0;
111             getOpNumArr(ExpTree, opNumArr, opNumArrIndex);
112 
113             tempExpInfo.opNumArr = opNumArr;
114             tempExpInfo.result = result;
115 
116 
117             if(ExpIndex == 1)    //第二条题目开始需要检测重复性
118                 addInExpArr(ExpArr, tempExpInfo, ExpIndex);
119             else{
120 
121                 if( !isRepetitive(ExpArr, tempExpInfo, ExpIndex) )
122                 {
123                     //5.2.1 合法的话,则加入到表达式数组中
124                     addInExpArr(ExpArr, tempExpInfo, ExpIndex);
125                     repetiveTime = 0;
126                 }
127                 else {
128                         //5.2.2 不合法的话,生成重复次数更新,释放空间,防止范围为2,题目数为10000的恶意输入
129                         repetiveTime++;
130                         if(repetiveTime >= ExpIndex)
131                             break;
132                         free(opNumArr);
133                         continue;
134                 }
135             }
136 
137             //6、在控制台、题目文件、答案文件打印题目序号
138             printf("%d.\t", ExpIndex);
139             fprintf(questionFile,"%d.\t", ExpIndex);
140             fprintf(answerFile, "%d.\t", ExpIndex);
141 
142             //6.1 表达式写入题目文件
143             writeExp(ExpTree, questionFile);
144             printf("= ");
145             fprintf(questionFile, "= \n");
146 
147             //6.2 写入答案文件
148             writeAnswer(result, answerFile);
149 
150             //6.3 控制台展示运算过程
151             //int flag = TRUE;
152             //getValue(ExpTree, flag, 1);
153             //printf("\n");
154 
155             //7、 更新题号
156             ExpIndex++;
157         }
158     }
159 
160     //8、 生成完毕,关闭文件,释放表达式数组空间
161     fclose(questionFile);
162     fclose(answerFile);
163     free(ExpArr);
164 
165     if(runtimeFlag == FALSE)
166     {
167         printf("空间申请失败...按任意键退出\n");    getchar();
168         exit(1);
169     }
170     printf("\n当前已生成 %d 道,操作数范围为 %d 的四则运算题目.\n", ExpIndex-1, numRange);
171     printf("题目保存在当前目录下的question.txt文件中\n答案保存在当前目录下的answer.txt文件中\n");
172     return 0;
173 }

 

下面是createExp.cpp代码:

  1 #include "Myapp.h"
  2 /**
  3  * @function    选择当前生成的表达式中的运算符
  4  * @param       symNum:     运算符数量
  5  *
  6  * @return      symArr:     选择的运算符数组
  7  */
  8 char* chooseSym(int symNum)
  9 {
 10     char* symArr = NULL;
 11     char sym[4] = {'+', '-', '*', '/'};
 12     symArr = (char*)malloc( symNum*sizeof(char));
 13     if(!symArr)
 14         return NULL;
 15 
 16     int i;
 17     for(i = 0; i < symNum; i++)
 18         symArr[i] = sym[rand()%4];
 19 
 20     return symArr;
 21 }
 22 
 23 /**
 24  * @function    根据传入的运算符数组索引创建运算符结点
 25  * @param       symArrIndex:    运算符数组索引;
 26                 symArr:         选择的运算符数组
 27  *
 28  * @return      nodePtr:        新创建运算符结点的指针
 29  */
 30 BiTNode* createSymNode(int& symArrIndex, char* symArr)
 31 {
 32     BiTNode* nodePtr = NULL;
 33     nodePtr = (BiTNode*) malloc(sizeof(BiTNode));
 34 
 35     if(!nodePtr)
 36         return NULL;
 37 
 38     nodePtr->flag = 0;
 39     nodePtr->value.opSym = symArr[symArrIndex++];
 40     nodePtr->rchild = nodePtr->lchild = NULL;
 41 
 42     return nodePtr;
 43 }
 44 
 45 /**
 46  * @function    根据传入的运算符数组组成一棵树
 47  * @param       symCount:     运算符数量;
 48                 symArr:选择的运算符数组
 49  *
 50  * @return      T:只有运算符的树根结点指针
 51  */
 52 BiTree createSymTree(int symCount, char* symArr)
 53 {
 54     BiTree root = NULL;
 55     int symArrIndex = 0, i;
 56     root = createSymNode(symArrIndex, symArr);
 57     if(!root)
 58         return NULL;
 59 
 60     for(i = 0; i < symCount-1; i++)
 61     {
 62         BiTNode* newNodePtr = createSymNode(symArrIndex, symArr);
 63         BiTNode* temp = root;
 64 
 65         while(1)
 66         {
 67             //根据随机数,随机生成运算符的树形结构
 68             if(rand()%2)
 69             {
 70                 if(!temp->lchild)
 71                 {
 72                     temp->lchild = newNodePtr;
 73                     break;
 74                 }
 75                 else temp = temp->lchild;
 76             }
 77             else {
 78                 if(!temp->rchild)
 79                 {
 80                     temp->rchild = newNodePtr;
 81                     break;
 82                 }
 83                 else temp = temp->rchild;
 84             }
 85         }
 86     }
 87     return root;
 88 }
 89 
 90 
 91 /**
 92  * @function    为传入的运算符树添加符合要求的操作数
 93  * @param       numRange:   操作数的范围
 94                 symTree:    运算符树
 95                 flag:       当前步骤是否成功的标记值,默认为TRUE
 96  *
 97  * @return      void
 98  */
 99 void addOpNumInSymTree(int numRange, BiTree symTree, int& flag)
100 {
101     //左孩子指针和右孩子指针都为空
102     if(!symTree->lchild && !symTree->rchild)
103     {
104         //范围大于1,随机生成操作数
105         if(numRange > 1)
106         {
107             int num1_numerator, num1_denominator;
108             int num2_numerator, num2_denominator;
109 
110             //如果运算符是-和/,操作数有要求
111             if( symTree->value.opSym == '-' )
112                 setNumForSubtraction(num1_numerator, num1_denominator, num2_numerator, num2_denominator, numRange);
113 
114             else if( symTree->value.opSym == '/' )
115                 setNumForDivision(num1_numerator, num1_denominator, num2_numerator, num2_denominator, numRange);
116 
117             else {
118                 getRandomNum(num1_numerator, num1_denominator, numRange);
119                 getRandomNum(num2_numerator, num2_denominator, numRange);
120             }
121 
122             symTree->lchild = createNumNode(num1_numerator, num1_denominator);
123             symTree->rchild = createNumNode(num2_numerator, num2_denominator);
124         }
125         else {
126             //范围等于1,操作数只能为0
127             symTree->lchild = createNumNode(0, 1);
128             symTree->rchild = createNumNode(0, 1);
129         }
130 
131         if(!symTree->lchild || !symTree->rchild)
132             flag = FALSE;
133 
134         return;
135     }
136 
137     //左孩子指针和右孩子指针都不为空
138     else if( symTree->lchild && symTree->rchild)
139     {
140         addOpNumInSymTree(numRange, symTree->lchild, flag);
141         addOpNumInSymTree(numRange, symTree->rchild, flag);
142     }
143 
144     //左孩子指针和右孩子指针其中一个为空
145     else
146     {
147         int _numerator, _denominator;
148         getRandomNum(_numerator, _denominator, numRange);
149         BiTNode* newNumNode = createNumNode(_numerator, _denominator);
150 
151         if(!newNumNode)
152             flag = FALSE;
153 
154         //左孩子指针为空,则插入左孩子,否则插入右孩子
155         if(!symTree->lchild){
156             symTree->lchild = newNumNode;
157             addOpNumInSymTree(numRange, symTree->rchild, flag);
158         }
159         else{
160             symTree->rchild = newNumNode;
161             addOpNumInSymTree(numRange, symTree->lchild, flag);
162         }
163     }
164 }
165 
166 /**
167  * @function     根据传入的数值创建运算数结点
168  * @param        numerator:     操作数的分子
169                  denominator:   操作数的分母
170  *
171  * @return       nodePtr:       操作数结点指针
172 */
173 BiTNode* createNumNode(int numerator, int denominator)
174 {
175     BiTNode* nodePtr = NULL;
176     nodePtr = (BiTree)malloc(sizeof(BiTNode));
177     if(!nodePtr)
178         return NULL;
179 
180     nodePtr->flag = 1;
181 
182     nodePtr->value.numForm.numerator = numerator;
183     nodePtr->value.numForm.denominator = denominator;
184 
185     nodePtr->lchild = nodePtr->rchild = NULL;
186     return nodePtr;
187 }
188 
189 /**
190  * @function    随机生成numRange范围内的操作数
191  * @param       numRange:       操作数的范围
192                 numerator:      操作数的分子部分
193                 denominator:    操作数的分母部分
194  *
195  * @return      void
196  */
197 void getRandomNum(int &numerator_, int &denominator_, int numRange)
198 {
199     //denominator 分母, numerator 分子
200     if(numRange != 1)
201     {
202         denominator_ = rand() % (numRange-1) + 1; // [1,numRange)
203         numerator_ = rand() % (numRange*denominator_); //[0, numRange*denominator)
204     }
205     else {
206         //范围numRange == 1, 操作数的分子只能为0, 分母设置为1
207         denominator_ = 1;
208         numerator_ = 0;
209     }
210 }
211 
212 /**
213  * @function    为_减法_设置两个符合要求的操作数
214  * @param       num1_numerator:     操作数1的分子
215                 num1_denominator:   操作数1的分母
216                 num2_numerator:     操作数2的分子
217                 num2_denominator:   操作数2的分母
218                 numRange:           操作数的范围
219  *
220  * @return      void
221  */
222 void setNumForSubtraction(int &num1_numerator, int &num1_denominator, int &num2_numerator, int &num2_denominator, int numRange)
223 {
224     double num1, num2;  //num1 - num2
225     do{
226         getRandomNum(num2_numerator, num2_denominator, numRange);
227         num2 = (double)num2_numerator / (double)num2_denominator;
228 
229         getRandomNum(num1_numerator, num1_denominator, numRange);
230         num1 = (double)num1_numerator / (double)num1_denominator;
231 
232     }while(num1 < num2);
233 }
234 
235 /**
236  * @function    为_除法_设置两个符合要求的操作数
237  * @param       num1_numerator:     操作数1的分子
238                 num1_denominator:   操作数1的分母
239                 num2_numerator:     操作数2的分子
240                 num2_denominator:   操作数2的分母
241                 numRange:           操作数的范围
242  *
243  * @return      void
244  */
245 void setNumForDivision(int &num1_numerator, int &num1_denominator, int &num2_numerator, int &num2_denominator, int numRange)
246 {
247     double num1, num2;  //num1 / num2
248     do{
249         num2_denominator = rand() % (numRange-1) + 1;  // [1,numRange)
250         num2_numerator = rand() % (numRange*num2_denominator - 1) + 1; // [1,numRange*num2_denominator) 除号情况除数不能为0这里需要处理
251         num2 = (double)num2_numerator / (double)num2_denominator;
252 
253         getRandomNum(num1_numerator, num1_denominator, numRange);
254         num1 = (double)num1_numerator / (double)num1_denominator;
255     }while(num1 > num2);
256 }

 

下面是judgeExp.cpp代码:

  1 #include "Myapp.h"
  2 /**
  3  * @function    表达式合法性检测,即能不能计算结果,能计算结果的返回计算结果
  4  * @param       ExpTree:    完整的表达式树
  5                 result:     存储合法表达式计算结果
  6  *
  7  * @return      flag:       当前表达式是否合法的标记
  8  */
  9 Status isLegalExp(BiTree ExpTree, NumInfo& result)
 10 {
 11     int flag = TRUE;
 12     result = getValue(ExpTree, flag, 0);
 13     return flag;
 14 }
 15 
 16 
 17 /**
 18  * @function    计算合法表达式的值,计算方式采用分数形式的计算
 19  * @param       ExpTree:    完整的表达式树
 20                 flag:       实时检测表达式是否合法的标记值
 21                 model:      控制台是否打印计算过程的标记值; 0:不打印计算过程, 1:打印
 22  *
 23  * @return      result:     当前表达式的计算结果
 24  */
 25 NumInfo getValue(BiTree ExpTree, int&flag, int model)
 26 {
 27     if(ExpTree->flag == 1)
 28         return ExpTree->value.numForm;
 29     else {
 30         NumInfo opNum1 = getValue(ExpTree->lchild, flag, model);
 31         NumInfo opNum2 = getValue(ExpTree->rchild, flag, model);
 32         NumInfo result;
 33 
 34         //通分,获得两个操作数分母的最小公倍数
 35         int LCM = findLCM(opNum1.denominator, opNum2.denominator);
 36 
 37         switch(ExpTree->value.opSym)
 38         {
 39             case '+' :  result.denominator = LCM;
 40                         result.numerator = opNum1.numerator*(LCM / opNum1.denominator) + opNum2.numerator*(LCM / opNum2.denominator);
 41                         break;
 42 
 43             case '-' :  result.denominator = LCM;
 44                         result.numerator = opNum1.numerator*(LCM / opNum1.denominator) - opNum2.numerator*(LCM / opNum2.denominator);
 45                         break;
 46 
 47             case '*' :
 48                         result.denominator = opNum1.denominator * opNum2.denominator;
 49                         result.numerator = opNum1.numerator * opNum2.numerator;
 50                         break;
 51 
 52             case '/' :
 53                         if(opNum2.numerator == 0)
 54                             flag = FALSE;
 55                         else {
 56                             result.denominator = opNum1.denominator * opNum2.numerator;
 57                             result.numerator = opNum1.numerator * opNum2.denominator;
 58                         }
 59                         break;
 60             default: ;
 61         }
 62 
 63         if(flag == TRUE)
 64         {
 65             result = simplify(result);
 66             if(model == 1)
 67                 printf("(%d/%d) %c (%d/%d) = %d/%d\n", opNum1.numerator, opNum1.denominator, ExpTree->value.opSym,
 68                                                     opNum2.numerator, opNum2.denominator, result.numerator, result.denominator);
 69         }
 70         else {
 71             result.numerator = 0;
 72             result.denominator = 1;
 73         }
 74         return result;
 75     }
 76 }
 77 
 78 /**
 79  * @function    寻找最大公约数,辗转相除法
 80  * @param       a:  参数1
 81                 b:  参数2
 82  *
 83  * @return      最大公约数
 84  */
 85 int findGCD(int a, int b)
 86 {
 87     if(a == 0)
 88         return b;
 89     return findGCD(b%a, a);
 90 }
 91 
 92 
 93 /**
 94  * @function    寻找最小公倍数,两数积除以最大公约数
 95  * @param       a:  参数1
 96                 b:  参数2
 97  *
 98  * @return      最小公倍数
 99  */
100 int findLCM(int a, int b)
101 {
102     return (a*b)/findGCD(a, b);
103 }
104 
105 
106 /**
107  * @function    化简分子和分母
108  * @param       numForm:    一个用分子/分母表示的数
109  *
110  * @return      afterSim:   化简后的分子/分母表示的数
111  */
112 NumInfo simplify(NumInfo numForm)
113 {
114     NumInfo afterSim;
115     if(numForm.numerator == 0)
116     {
117         afterSim.numerator = 0;
118         afterSim.denominator = 1;
119         return afterSim;
120     }
121 
122     //寻找最大公因数
123     int GCD = findGCD(numForm.denominator, abs(numForm.numerator));
124     afterSim.numerator = numForm.numerator / GCD;
125     afterSim.denominator = numForm.denominator / GCD;
126     return afterSim;
127 }
128 
129 
130 /**
131  * @function    检测当前生成的表达式是否与前面的题目重复
132  * @param       ExpArr:         表达式数组
133                 ExpTree:        当前表达式树
134                 ExpIndex:       表达式数组的最后一个元素的索引
135  *
136  * @return      flag:   当前表达式是否与前面题目重复的标记值  FALSE:不重复,    TRUE:重复
137  */
138 Status isRepetitive(ExpInfoPtr ExpArr, ExpInfo tempExpInfo, int ExpIndex)
139 {
140     int flag = FALSE;
141     int i;
142     int tempExpInfoOpNumCount = tempExpInfo.symCount+1;
143 
144     for(i = 1; i < ExpIndex; i++)
145     {
146         ExpInfo temp = ExpArr[i];
147         //1、比较表达式运算符数量
148         if( tempExpInfo.symCount == temp.symCount )
149             //2、比较表达式结果,分子分母对应相等
150             if( tempExpInfo.result.denominator == temp.result.denominator && tempExpInfo.result.numerator == temp.result.numerator)
151                     //3、比较表达式的运算符数组
152                     if( symArrCmp(tempExpInfo.symArr, temp.symArr, tempExpInfo.symCount) )
153                         //4、比较表达式的操作数数组
154                         if( opNumArrCmp(tempExpInfo.opNumArr, temp.opNumArr, tempExpInfoOpNumCount) )
155                             //5、比较表达式树的结构
156                             if( ExpTreeCmp(tempExpInfo.ExpTree, temp.ExpTree) )
157                             {
158                                 flag = TRUE;
159                                 break;
160                             }
161     }
162     return flag;
163 }
164 
165 
166 /**
167  * @function    检测表达式1的树结构和表达式2是否有重复关系
168  * @param       T1:     表达式树1
169                 T2:     表达式树2
170  *
171  * @return      flag:   表达式1和表达式2是否有重复关系的标记值, FLASE:不重复,    TRUE:重复
172  */
173 Status ExpTreeCmp(BiTree T1, BiTree T2)
174 {
175     if(T1->flag ==  T2->flag){ //结点类型相同
176         if(T1->flag == 1){ //都是操作数结点
177             if(T1->value.numForm.numerator==0 && T2->value.numForm.numerator==0)
178                 return TRUE;
179             else if(T1->value.numForm.numerator == T2->value.numForm.numerator
180                && T1->value.numForm.denominator == T2->value.numForm.denominator)
181                 return TRUE;
182             else
183                 return FALSE;
184         }
185         else if(T1->flag == 0){ //都是操作符结点
186             if(T1->value.opSym != T2->value.opSym)
187                 return FALSE;
188             // 操作符相同才递归比较
189             else{
190                 if(T1->value.opSym == '*' || T1->value.opSym == '+'){
191                     int flag1, flag2;
192                     flag1 = ExpTreeCmp(T1->lchild, T2->lchild) && ExpTreeCmp(T1->rchild, T2->rchild); //比较左左和右右的结果
193                     flag2 = ExpTreeCmp(T1->lchild, T2->rchild) && ExpTreeCmp(T1->rchild, T2->lchild);  // 比较左右和右左的结果
194                     return flag1 || flag2; //其中某一种相同则相同
195                 }
196                 else
197                     return ExpTreeCmp(T1->lchild, T2->lchild) && ExpTreeCmp(T1->rchild, T2->rchild);
198             }
199         }
200     }
201     else
202         return FALSE;
203 
204 }
205 
206 /**
207  * @function    检测运算符数组1和数组2是否有重复关系
208  * @param       arr1:     运算符数组1
209                 arr2:     运算符数组2
210                 length:   数组长度
211  *
212  * @return      flag:   运算符数组1和数组2是否有重复关系的标记值, FALSE:不重复,    TRUE:重复
213  */
214 Status symArrCmp(char* arr1, char* arr2, int length)
215 {
216     //由于是乱序的数组比较,采用辅助标记数组的方式
217     int* flagArr = (int*) malloc(length * sizeof(int));
218     if(!flagArr)
219         return FALSE;
220 
221     int i, j;
222     for(i = 0; i < length; i++)
223         flagArr[i] = 0;
224 
225     for(i = 0; i < length; i++)
226     {
227         for(j = 0; j < length; j++)
228             if(arr1[i] == arr2[j] && flagArr[j] == 0 )
229             {
230                 flagArr[j] = 1;
231                 break;
232             }
233     }
234 
235     for(i = 0; i < length; i++)
236         if(flagArr[i] != 1)
237             return FALSE;
238 
239     return TRUE;
240 }
241 
242 /**
243  * @function    检测操作数数组1和数组2是否有重复关系
244  * @param       arr1:     操作数数组1
245                 arr2:     操作数数组2
246                 length:   数组长度
247  *
248  * @return      flag:     操作数数组1和数组2是否有重复关系的标记值, FALSE:不重复,    TRUE:重复
249  */
250 Status opNumArrCmp(NumInfo* arr1, NumInfo* arr2, int length)
251 {
252     //由于是乱序的数组比较,采用辅助标记数组的方式
253     int* flagArr = (int*) malloc(length * sizeof(int));
254     if(!flagArr)
255         return FALSE;
256 
257     int i, j;
258     for(i = 0; i < length; i++)
259         flagArr[i] = 0;
260 
261     for(i = 0; i < length; i++)
262     {
263         for(j = 0; j < length; j++)
264             if(arr1[i].numerator == arr2[j].numerator &&
265                     arr1[i].denominator == arr2[j].denominator && flagArr[j] == 0)
266             {
267                 flagArr[j] = 1;
268                 break;
269             }
270     }
271 
272     for(i = 0; i < length; i++)
273         if(flagArr[i] != 1)
274             return FALSE;
275 
276     return TRUE;
277 }
278 
279 /**
280  * @function    把当前表达式的基本信息存入表达式数组中
281  * @param       ExpArr:         表达式数组
282                 ExpInfo:        当前表达式的基本信息
283 
284  * @return      void
285  */
286 void addInExpArr(ExpInfoPtr ExpArr, ExpInfo tempExpInfo, int ExpIndex)
287 {
288     ExpArr[ExpIndex].ExpTree = tempExpInfo.ExpTree;
289     ExpArr[ExpIndex].symCount = tempExpInfo.symCount;
290     ExpArr[ExpIndex].symArr = tempExpInfo.symArr;
291     ExpArr[ExpIndex].opNumArr = tempExpInfo.opNumArr;
292     ExpArr[ExpIndex].result = tempExpInfo.result;
293 }
294 
295 
296 /**
297  * @function    获取当前表达式的操作数,并记录在一个数组中
298  * @param       ExpTree:        表达式树
299                 opNumArr:       操作数数组指针
300                 opNumArrIndex:  操作数数组索引
301  *
302  * @return      void
303  */
304 void getOpNumArr(BiTree ExpTree, NumInfo* opNumArr, int& opNumArrIndex)
305 {
306     if(ExpTree->flag == 1)
307         opNumArr[opNumArrIndex++] = ExpTree->value.numForm;
308 
309     else{
310         getOpNumArr(ExpTree->lchild, opNumArr, opNumArrIndex);
311         getOpNumArr(ExpTree->rchild, opNumArr, opNumArrIndex);
312     }
313 }

 

下面是writeExpAndAnswer.cpp的代码:

  1 #include "Myapp.h"
  2 /**
  3  * @function    比较运算符优先级
  4  * @param       运算符c1, c2
  5  *
  6  * @return      Bool值
  7  */
  8 Status Priority_com(char c1,char c2){
  9     if((c1 == '+' || c1 == '-' || c1 == '*' || c1 == '/') && (c2 == '+' || c2 == '-' || c2 == '*' || c2 == '/'))
 10     {
 11         if(c1 == '*' || c1 == '/')
 12         {
 13             if(c2 == '+' || c2 == '-')
 14                 return TRUE;
 15             else
 16                 return FALSE;
 17         }
 18         else
 19             return FALSE;
 20     }
 21     else
 22         return FALSE;
 23 }
 24 
 25 
 26 /**
 27  * @function    将当前表达式写入题目文件
 28  * @param       T:      表达式树
 29                 file:   题目文件指针
 30  *
 31  * @return      void
 32  */
 33 void writeExp(BiTree T, FILE* file)
 34 {
 35     if(!T)
 36         return;
 37 
 38     if(T->lchild && T->lchild->flag == 0){ //如果根节点与左子树的根节点是运算符
 39         if(Priority_com(T->value.opSym, T->lchild->value.opSym)){
 40             printf("( ");
 41             fprintf(file,"( ");
 42             writeExp(T->lchild, file);
 43             printf(") ");
 44             fprintf(file,") ");
 45         }
 46         else
 47             writeExp(T->lchild, file);
 48     }
 49     else
 50         writeExp(T->lchild, file);
 51 
 52 
 53     if(T->flag == 0){
 54         printf("%c  ",T->value.opSym);
 55         fprintf(file,"%c  ",T->value.opSym);
 56     }
 57     else {
 58         int numerator = T->value.numForm.numerator;
 59         int denominator = T->value.numForm.denominator;
 60 
 61         if(numerator % denominator == 0){ //自然数
 62             printf("%d  ",numerator/denominator);
 63             fprintf(file,"%d  ",numerator/denominator);
 64         }
 65         else{
 66             if(numerator < denominator){ //真分数
 67                 printf("%d/%d  ",numerator,denominator);
 68                 fprintf(file,"%d/%d  ",numerator,denominator);
 69             }
 70             else{ //带分数
 71                 printf("%d\'%d/%d  ",numerator/denominator,numerator%denominator,denominator);
 72                 fprintf(file,"%d\'%d/%d  ",numerator/denominator,numerator%denominator,denominator);
 73             }
 74         }
 75     }
 76 
 77     if(T->rchild && T->rchild->flag == 0){ //如果根节点与右子树的根节点是运算符
 78             printf("( ");
 79             fprintf(file,"(  ");
 80             writeExp(T->rchild, file);
 81             printf(") ");
 82             fprintf(file,")  ");
 83     }
 84     else
 85         writeExp(T->rchild, file);
 86 }
 87 
 88 /**
 89  * @function    将当前表达式写入答案文件
 90  * @param       T:      表达式树
 91                 file:   答案文件指针
 92  *
 93  * @return      void
 94  */
 95 void writeAnswer(NumInfo result, FILE* answerFile)
 96 {
 97     int numerator = result.numerator;
 98     int denominator = result.denominator;
 99     if(numerator % denominator == 0){ //自然数
100         printf("%d\n",numerator/denominator);
101         fprintf(answerFile,"%d\n",numerator/denominator);
102     }
103     else{
104         if(abs(numerator) < abs(denominator)){ //真分数
105             printf("%d/%d\n",numerator,denominator);
106             fprintf(answerFile,"%d/%d\n",numerator,denominator);
107         }
108         else{ //带分数
109             printf("%d\'%d/%d\n",numerator/denominator,abs(numerator)%denominator,denominator);
110             fprintf(answerFile,"%d\'%d/%d\n",numerator/denominator,abs(numerator)%denominator,denominator);
111         }
112     }
113 }

 


 

测试运行

  

     

 

项目小结

  这次项目是结对项目,需要两个人的合作,一个人编码,一个人设计复审,角色轮流交换。每个阶段都有不同的责任,项目采用功能分块,由简入繁的过程开发,还记得最初的版本,只有一个main函数,两个调用的函数,没用命令行,参数固定,再到后面参数改变,不断测试,有时在测试中找到的是之前写的函数的bug,属实尴尬,不过通过不断的代码复审,代码阅读,时常灵光一闪,这段删掉,这段补上,if-else丢得只剩一个,逻辑简化,效果更佳。也有讨论到入夜已深,手机常亮,想法迸发,落魄发现,方案搞笑,打回重想。但是,个中滋味,回味如甘,编码乐趣,在于敲下。

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!