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需求:

(完成)使用 -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

四则运算题目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

计划

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#include#include#include#include

#define TRUE 1

#define FALSE 0typedefintStatus;//操作数结构体定义

typedef structNumInfo{//denominator 分母, numerator 分子

intnumerator;intdenominator;

} NumInfo;//表达式树结点定义

typedef structBiTNode{

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(intsymNum);

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, intnumRange);void setNumForDivision(int &num1_numerator, int &num1_denominator, int &num2_numerator, int &num2_denominator, intnumRange);void getRandomNum(int &numerator_, int &denominator_, intnumRange);

BiTNode* createNumNode(int numerator, intdenominator);//检查表达式是否合法的函数声明,包含计算表达式的函数

Status isLegalExp(BiTree ExpTree, NumInfo&result);

NumInfo getValue(BiTree ExpTree,int&flag, intmodel);int findGCD(int a, intb);int findLCM(int a, intb);

NumInfo simplify(NumInfo numForm);//检测是否为重复的表达式的函数声明

Status isRepetitive(ExpInfoPtr ExpArr, ExpInfo tempExpInfo, intExpIndex);

Status ExpTreeCmp(BiTree T1, BiTree T2);

Status symArrCmp(char* arr1, char* arr2, intlength);

Status opNumArrCmp(NumInfo* arr1, NumInfo* arr2, intlength);void addInExpArr(ExpInfoPtr ExpArr, ExpInfo tempExpInfo, intExpIndex);void getOpNumArr(BiTree ExpTree, NumInfo* OpNumArr, int&OpNumArrIndex);//题目和答案文件读写函数的声明

Status Priority_com(char c1, charc2);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 inti;9 for(i = 0; i < strlen(str); i++)10 if(str[i] < '0' || str[i] > '9')11 returnFALSE;12 returnTRUE;13 }14

15 int main(int argc, char *argv[])16 {17 int questionCount = 0, numRange = 0;

18

19 intopt;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(intsymNum)9 {10 char* symArr =NULL;11 char sym[4] = {'+', '-', '*', '/'};12 symArr = (char*)malloc( symNum*sizeof(char));13 if(!symArr)14 returnNULL;15

16 inti;17 for(i = 0; i < symNum; i++)18 symArr[i] = sym[rand()%4];19

20 returnsymArr;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 returnNULL;37

38 nodePtr->flag = 0;39 nodePtr->value.opSym = symArr[symArrIndex++];40 nodePtr->rchild = nodePtr->lchild =NULL;41

42 returnnodePtr;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 returnNULL;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 returnroot;88 }89

90

91 /**92 * @function 为传入的运算符树添加符合要求的操作数93 * @param numRange: 操作数的范围94 symTree: 运算符树95 flag: 当前步骤是否成功的标记值,默认为TRUE96 *97 * @return void98 */

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 intnum1_numerator, num1_denominator;108 intnum2_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, intdenominator)174 {175 BiTNode* nodePtr =NULL;176 nodePtr = (BiTree)malloc(sizeof(BiTNode));177 if(!nodePtr)178 returnNULL;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 returnnodePtr;187 }188

189 /**190 * @function 随机生成numRange范围内的操作数191 * @param numRange: 操作数的范围192 numerator: 操作数的分子部分193 denominator: 操作数的分母部分194 *195 * @return void196 */

197 void getRandomNum(int &numerator_, int &denominator_, intnumRange)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 void221 */

222 void setNumForSubtraction(int &num1_numerator, int &num1_denominator, int &num2_numerator, int &num2_denominator, intnumRange)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

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 void244 */

245 void setNumForDivision(int &num1_numerator, int &num1_denominator, int &num2_numerator, int &num2_denominator, intnumRange)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 returnflag;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, intmodel)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 returnresult;75 }76 }77

78 /**79 * @function 寻找最大公约数,辗转相除法80 * @param a: 参数181 b: 参数282 *83 * @return 最大公约数84 */

85 int findGCD(int a, intb)86 {87 if(a == 0)88 returnb;89 return findGCD(b%a, a);90 }91

92

93 /**94 * @function 寻找最小公倍数,两数积除以最大公约数95 * @param a: 参数196 b: 参数297 *98 * @return 最小公倍数99 */

100 int findLCM(int a, intb)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 returnafterSim;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 returnafterSim;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, intExpIndex)139 {140 int flag =FALSE;141 inti;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 returnflag;163 }164

165

166 /**167 * @function 检测表达式1的树结构和表达式2是否有重复关系168 * @param T1: 表达式树1169 T2: 表达式树2170 *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 returnTRUE;179 else if(T1->value.numForm.numerator == T2->value.numForm.numerator180 && T1->value.numForm.denominator == T2->value.numForm.denominator)181 returnTRUE;182 else

183 returnFALSE;184 }185 else if(T1->flag == 0){ //都是操作符结点

186 if(T1->value.opSym != T2->value.opSym)187 returnFALSE;188 //操作符相同才递归比较

189 else{190 if(T1->value.opSym == '*' || T1->value.opSym == '+'){191 intflag1, 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 returnFALSE;203

204 }205

206 /**207 * @function 检测运算符数组1和数组2是否有重复关系208 * @param arr1: 运算符数组1209 arr2: 运算符数组2210 length: 数组长度211 *212 * @return flag: 运算符数组1和数组2是否有重复关系的标记值, FALSE:不重复, TRUE:重复213 */

214 Status symArrCmp(char* arr1, char* arr2, intlength)215 {216 //由于是乱序的数组比较,采用辅助标记数组的方式

217 int* flagArr = (int*) malloc(length * sizeof(int));218 if(!flagArr)219 returnFALSE;220

221 inti, 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 returnFALSE;238

239 returnTRUE;240 }241

242 /**243 * @function 检测操作数数组1和数组2是否有重复关系244 * @param arr1: 操作数数组1245 arr2: 操作数数组2246 length: 数组长度247 *248 * @return flag: 操作数数组1和数组2是否有重复关系的标记值, FALSE:不重复, TRUE:重复249 */

250 Status opNumArrCmp(NumInfo* arr1, NumInfo* arr2, intlength)251 {252 //由于是乱序的数组比较,采用辅助标记数组的方式

253 int* flagArr = (int*) malloc(length * sizeof(int));254 if(!flagArr)255 returnFALSE;256

257 inti, 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 returnFALSE;275

276 returnTRUE;277 }278

279 /**280 * @function 把当前表达式的基本信息存入表达式数组中281 * @param ExpArr: 表达式数组282 ExpInfo: 当前表达式的基本信息283

284 * @return void285 */

286 void addInExpArr(ExpInfoPtr ExpArr, ExpInfo tempExpInfo, intExpIndex)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 void303 */

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, c25 *6 * @return Bool值7 */

8 Status Priority_com(char c1,charc2){9 if((c1 == '+' || c1 == '-' || c1 == '*' || c1 == '/') && (c2 == '+' || c2 == '-' || c2 == '*' || c2 == '/'))10 {11 if(c1 == '*' || c1 == '/')12 {13 if(c2 == '+' || c2 == '-')14 returnTRUE;15 else

16 returnFALSE;17 }18 else

19 returnFALSE;20 }21 else

22 returnFALSE;23 }24

25

26 /**27 * @function 将当前表达式写入题目文件28 * @param T: 表达式树29 file: 题目文件指针30 *31 * @return void32 */

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 void94 */

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丢得只剩一个,逻辑简化,效果更佳。也有讨论到入夜已深,手机常亮,想法迸发,落魄发现,方案搞笑,打回重想。但是,个中滋味,回味如甘,编码乐趣,在于敲下。

c语言指针化简带分数,自动生成四则运算题目(C语言)相关推荐

  1. c语言指针化简带分数,c语言带分数四则运算问题!!!要求被调函数,化简.

    满意答案 Lie囡 2013.02.03 采纳率:58%    等级:12 已帮助:8948人 #include long int a,b,c,d,x,y,z; char op; int f1(); ...

  2. python实现四则运算_自动生成四则运算题目(python实现)

    自动生成四则运算题目(python实现) 项目分析 项目仓库: 地址 需求 仅包含四则运算 结果不能为负数 数字大小在 100 以内 支持真分数运算 设计实现过程及代码说明 项目文件结构如下: 模块 ...

  3. 自动生成四则运算题目

    主要功能:本程序可生成使用者想要完成的题目数量的题目,程序内生成的题目中包含整数计算和真分数计算两种.程序的设计对象是小学一年级到三年级学生,通过程序生成的题目来检验整数和真分数的四则运算. 设计思想 ...

  4. 软件工程关于自动生成四则运算题目程序的见解

    1.需求分析 一个程序员在编写程序前第一件事就是要知道我要编写的程序是要干什么的,它要那些功能. 题目中要求一个能自动生成小学四则运算题目的程序,分析起来很简单,做起来也很简单. 2.实施 由于是小学 ...

  5. 写一个能自动生成四则运算题目的软件,要求除了整数,还要支持正分数的四则运算。和同学们比较各自的程序功能、实现方法的异同。...

    package Rational; import java.util.Random; import java.util.Scanner; public class szys {             ...

  6. 结对作业2——自动生成四则运算

    一. 选择题目 我们在刚开始上课的时候介绍过一个小学四则运算自动生成程序的例子,请实现它,要求: 能够自动生成四则运算练习题 可以定制题目数量 用户可以选择运算符 用户设置最大数(如十以内.百以内等) ...

  7. 个人项目一:自动生成四则运算题目的“软件”

    使用软件:Microsoft Visual Studio 2010 使用语言:C# 设计思路: 1.首先,确定要使用的窗体,用了3个TextBox窗体,3个Button窗体和一个RichTextBox ...

  8. 国家c语言二级机试题,国家二级计算机考试题目C语言机试题库.doc

    国家二级计算机考试题目C语言机试题库.doc 国家计算机等级考试二级 c 语言考试机考题目题目有 1.2.3 .4 个数字,能组成多少个互不相同且无重复数字的三位数 都是多少1.程序分析可填在百位.十 ...

  9. oss生成唯一文件名_根据结构化自然语言规范自动生成精确预言

    1 引用 Tomassi D A, Dmeiri N, Wang Y, et al. Bugswarm: mining and continuously growing a dataset of re ...

最新文章

  1. jquery实现返回顶部按钮和scroll滚动功能[带动画效果] 转载
  2. 无废话ADO.NET 第二章 关于连接
  3. Hover伪类在IE6中的实现
  4. 源代码管理-SVN自动更新
  5. Boost:gzifstream和gzofstream的测试程序
  6. python标准库对象导入语句_Python标准库之Sys模块使用详解
  7. 至2015新的一年!
  8. Ubuntu18.04-安装opencv3.2.0_contrib
  9. NHibernate Step By Step(6)-关联关系
  10. 《21天学通Java(第6版)》—— 2.11 练习
  11. Java基础——学生管理系统
  12. Relatively Prime Pairs
  13. Tableau实战 Tableau官网各版块访问情况(四)各网址情况分析
  14. ROS机器人(一) -- 环境搭建
  15. Normal模式下ASM中的空间参数解析
  16. Android Kotlin - 监听耳机的插入和拔出
  17. Google Chrome谷歌浏览器崩溃,错误代码: STATUS_STACK_BUFFER_OVERRUN
  18. 奥西300工程机服务器装系统,奥西pw300驱动
  19. 做电影出品人的秘密,你知道多少!
  20. 面向对象程序设计的五个基本原则

热门文章

  1. WESTCAR系列的液力偶合器rotofluid、rotomec、kda
  2. 赛桨PaddleScience v1.0 Beta:基于飞桨核心框架的科学计算通用求解器
  3. 如何把数字金额转换成中文大写
  4. Vmware 虚拟机安装 Ubuntu
  5. 手机连接投影机的步骤_投影仪如何投屏手机?手机连接投影仪的步骤
  6. win7系统无法连接局域网服务器,Win7局域网无法访问如何解决?
  7. Dewarp 文件恢复技术
  8. https请求报Connection reset问题
  9. Android Hierarchy Viewer
  10. 计算机 修改 虚拟ip,怎么样在电脑中设置虚拟IP地址?