自动生成四则运算题目(C语言)
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 <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函数:
#include "Myapp.h"
#include "createExp.cpp"
#include "judgeExp.cpp"
#include "writeExpAndAnswer.cpp" Status isNumString(char* str)
{
int i;
for(i = ; i < strlen(str); i++)
if(str[i] < '' || str[i] > '')
return FALSE;
return TRUE;
} int main(int argc, char *argv[])
{
int questionCount = , numRange = ; int opt;
while((opt=getopt(argc,argv,"n:r:"))!=-)
{
switch(opt)
{
case 'n': if(isNumString(optarg)) questionCount = atoi(optarg);
break;
case 'r': if(isNumString(optarg)) numRange = atoi(optarg);
break;
default: ;
}
}
if( questionCount == || numRange == )
{
printf("命令行参数输入错误...\n");
printf("输入格式为Myapp.exe -n 题目数(整数) -r 范围值(整数)\n");
exit();
}
srand((int)time()); FILE* questionFile, *answerFile;
questionFile = fopen("question.txt", "w");
answerFile = fopen("answer.txt", "w");
if(!questionFile || !answerFile)
{
printf("文件打开失败...按任意键退出\n"); getchar();
exit();
} ExpInfoPtr ExpArr = NULL;
ExpArr = (ExpInfoPtr)malloc( (questionCount+) * sizeof(ExpInfo));
if(!ExpArr)
{
printf("空间申请失败...按任意键退出\n"); getchar();
exit();
} int ExpIndex = ;
int repetiveTime = ;
int runtimeFlag = TRUE; while(ExpIndex <= questionCount)
{
//1、先生成运算符数量,再随机选择运算符
int symCount = rand()% +; //一条表达式最多选择3个运算符
char* symArr = chooseSym(symCount);
if(!symArr)
{
runtimeFlag = FALSE;
break;
} //2、运算符搭成树
BiTree symTree = createSymTree(symCount, symArr);
if(!symTree)
{
runtimeFlag = FALSE;
break;
} //3、在运算符树的叶子结点加操作数
int addFlag = TRUE;
addOpNumInSymTree(numRange, symTree, addFlag);
if(addFlag == FALSE)
{
runtimeFlag = FALSE;
break;
} //4、判断生成的表达式是否合法,主要检测除数是否为0,并进行文件读写
BiTree ExpTree = symTree;
NumInfo result; if( isLegalExp(ExpTree, result) )
{
//5、表达式合法则检查重复性
//5.1 先获取当前表达式的基本信息
ExpInfo tempExpInfo;
tempExpInfo.ExpTree = ExpTree;
tempExpInfo.symCount = symCount;
tempExpInfo.symArr = symArr; int opNumCount = symCount+;
NumInfo* opNumArr = (NumInfo*)malloc( opNumCount*sizeof(NumInfo));
if(!opNumArr)
{
runtimeFlag = FALSE;
break;
} int opNumArrIndex = ;
getOpNumArr(ExpTree, opNumArr, opNumArrIndex); tempExpInfo.opNumArr = opNumArr;
tempExpInfo.result = result; if(ExpIndex == ) //第二条题目开始需要检测重复性
addInExpArr(ExpArr, tempExpInfo, ExpIndex);
else{ if( !isRepetitive(ExpArr, tempExpInfo, ExpIndex) )
{
//5.2.1 合法的话,则加入到表达式数组中
addInExpArr(ExpArr, tempExpInfo, ExpIndex);
repetiveTime = ;
}
else {
//5.2.2 不合法的话,生成重复次数更新,释放空间,防止范围为2,题目数为10000的恶意输入
repetiveTime++;
if(repetiveTime >= ExpIndex)
break;
free(opNumArr);
continue;
}
} //6、在控制台、题目文件、答案文件打印题目序号
printf("%d.\t", ExpIndex);
fprintf(questionFile,"%d.\t", ExpIndex);
fprintf(answerFile, "%d.\t", ExpIndex); //6.1 表达式写入题目文件
writeExp(ExpTree, questionFile);
printf("= ");
fprintf(questionFile, "= \n"); //6.2 写入答案文件
writeAnswer(result, answerFile); //6.3 控制台展示运算过程
//int flag = TRUE;
//getValue(ExpTree, flag, 1);
//printf("\n"); //7、 更新题号
ExpIndex++;
}
} //8、 生成完毕,关闭文件,释放表达式数组空间
fclose(questionFile);
fclose(answerFile);
free(ExpArr); if(runtimeFlag == FALSE)
{
printf("空间申请失败...按任意键退出\n"); getchar();
exit();
}
printf("\n当前已生成 %d 道,操作数范围为 %d 的四则运算题目.\n", ExpIndex-1, numRange);
printf("题目保存在当前目录下的question.txt文件中\n答案保存在当前目录下的answer.txt文件中\n");
return ;
}
下面是createExp.cpp代码:
#include "Myapp.h"
/**
* @function 选择当前生成的表达式中的运算符
* @param symNum: 运算符数量
*
* @return symArr: 选择的运算符数组
*/
char* chooseSym(int symNum)
{
char* symArr = NULL;
char sym[] = {'+', '-', '*', '/'};
symArr = (char*)malloc( symNum*sizeof(char));
if(!symArr)
return NULL; int i;
for(i = ; i < symNum; i++)
symArr[i] = sym[rand()%]; return symArr;
} /**
* @function 根据传入的运算符数组索引创建运算符结点
* @param symArrIndex: 运算符数组索引;
symArr: 选择的运算符数组
*
* @return nodePtr: 新创建运算符结点的指针
*/
BiTNode* createSymNode(int& symArrIndex, char* symArr)
{
BiTNode* nodePtr = NULL;
nodePtr = (BiTNode*) malloc(sizeof(BiTNode)); if(!nodePtr)
return NULL; nodePtr->flag = ;
nodePtr->value.opSym = symArr[symArrIndex++];
nodePtr->rchild = nodePtr->lchild = NULL; return nodePtr;
} /**
* @function 根据传入的运算符数组组成一棵树
* @param symCount: 运算符数量;
symArr:选择的运算符数组
*
* @return T:只有运算符的树根结点指针
*/
BiTree createSymTree(int symCount, char* symArr)
{
BiTree root = NULL;
int symArrIndex = , i;
root = createSymNode(symArrIndex, symArr);
if(!root)
return NULL; for(i = ; i < symCount-; i++)
{
BiTNode* newNodePtr = createSymNode(symArrIndex, symArr);
BiTNode* temp = root; while()
{
//根据随机数,随机生成运算符的树形结构
if(rand()%)
{
if(!temp->lchild)
{
temp->lchild = newNodePtr;
break;
}
else temp = temp->lchild;
}
else {
if(!temp->rchild)
{
temp->rchild = newNodePtr;
break;
}
else temp = temp->rchild;
}
}
}
return root;
} /**
* @function 为传入的运算符树添加符合要求的操作数
* @param numRange: 操作数的范围
symTree: 运算符树
flag: 当前步骤是否成功的标记值,默认为TRUE
*
* @return void
*/
void addOpNumInSymTree(int numRange, BiTree symTree, int& flag)
{
//左孩子指针和右孩子指针都为空
if(!symTree->lchild && !symTree->rchild)
{
//范围大于1,随机生成操作数
if(numRange > )
{
int num1_numerator, num1_denominator;
int num2_numerator, num2_denominator; //如果运算符是-和/,操作数有要求
if( symTree->value.opSym == '-' )
setNumForSubtraction(num1_numerator, num1_denominator, num2_numerator, num2_denominator, numRange); else if( symTree->value.opSym == '/' )
setNumForDivision(num1_numerator, num1_denominator, num2_numerator, num2_denominator, numRange); else {
getRandomNum(num1_numerator, num1_denominator, numRange);
getRandomNum(num2_numerator, num2_denominator, numRange);
} symTree->lchild = createNumNode(num1_numerator, num1_denominator);
symTree->rchild = createNumNode(num2_numerator, num2_denominator);
}
else {
//范围等于1,操作数只能为0
symTree->lchild = createNumNode(, );
symTree->rchild = createNumNode(, );
} if(!symTree->lchild || !symTree->rchild)
flag = FALSE; return;
} //左孩子指针和右孩子指针都不为空
else if( symTree->lchild && symTree->rchild)
{
addOpNumInSymTree(numRange, symTree->lchild, flag);
addOpNumInSymTree(numRange, symTree->rchild, flag);
} //左孩子指针和右孩子指针其中一个为空
else
{
int _numerator, _denominator;
getRandomNum(_numerator, _denominator, numRange);
BiTNode* newNumNode = createNumNode(_numerator, _denominator); if(!newNumNode)
flag = FALSE; //左孩子指针为空,则插入左孩子,否则插入右孩子
if(!symTree->lchild){
symTree->lchild = newNumNode;
addOpNumInSymTree(numRange, symTree->rchild, flag);
}
else{
symTree->rchild = newNumNode;
addOpNumInSymTree(numRange, symTree->lchild, flag);
}
}
} /**
* @function 根据传入的数值创建运算数结点
* @param numerator: 操作数的分子
denominator: 操作数的分母
*
* @return nodePtr: 操作数结点指针
*/
BiTNode* createNumNode(int numerator, int denominator)
{
BiTNode* nodePtr = NULL;
nodePtr = (BiTree)malloc(sizeof(BiTNode));
if(!nodePtr)
return NULL; nodePtr->flag = ; nodePtr->value.numForm.numerator = numerator;
nodePtr->value.numForm.denominator = denominator; nodePtr->lchild = nodePtr->rchild = NULL;
return nodePtr;
} /**
* @function 随机生成numRange范围内的操作数
* @param numRange: 操作数的范围
numerator: 操作数的分子部分
denominator: 操作数的分母部分
*
* @return void
*/
void getRandomNum(int &numerator_, int &denominator_, int numRange)
{
//denominator 分母, numerator 分子
if(numRange != )
{
denominator_ = rand() % (numRange-) + ; // [1,numRange)
numerator_ = rand() % (numRange*denominator_); //[0, numRange*denominator)
}
else {
//范围numRange == 1, 操作数的分子只能为0, 分母设置为1
denominator_ = ;
numerator_ = ;
}
} /**
* @function 为_减法_设置两个符合要求的操作数
* @param num1_numerator: 操作数1的分子
num1_denominator: 操作数1的分母
num2_numerator: 操作数2的分子
num2_denominator: 操作数2的分母
numRange: 操作数的范围
*
* @return void
*/
void setNumForSubtraction(int &num1_numerator, int &num1_denominator, int &num2_numerator, int &num2_denominator, int numRange)
{
double num1, num2; //num1 - num2
do{
getRandomNum(num2_numerator, num2_denominator, numRange);
num2 = (double)num2_numerator / (double)num2_denominator; getRandomNum(num1_numerator, num1_denominator, numRange);
num1 = (double)num1_numerator / (double)num1_denominator; }while(num1 < num2);
} /**
* @function 为_除法_设置两个符合要求的操作数
* @param num1_numerator: 操作数1的分子
num1_denominator: 操作数1的分母
num2_numerator: 操作数2的分子
num2_denominator: 操作数2的分母
numRange: 操作数的范围
*
* @return void
*/
void setNumForDivision(int &num1_numerator, int &num1_denominator, int &num2_numerator, int &num2_denominator, int numRange)
{
double num1, num2; //num1 / num2
do{
num2_denominator = rand() % (numRange-) + ; // [1,numRange)
num2_numerator = rand() % (numRange*num2_denominator - ) + ; // [1,numRange*num2_denominator) 除号情况除数不能为0这里需要处理
num2 = (double)num2_numerator / (double)num2_denominator; getRandomNum(num1_numerator, num1_denominator, numRange);
num1 = (double)num1_numerator / (double)num1_denominator;
}while(num1 > num2);
}
下面是judgeExp.cpp代码:
#include "Myapp.h"
/**
* @function 表达式合法性检测,即能不能计算结果,能计算结果的返回计算结果
* @param ExpTree: 完整的表达式树
result: 存储合法表达式计算结果
*
* @return flag: 当前表达式是否合法的标记
*/
Status isLegalExp(BiTree ExpTree, NumInfo& result)
{
int flag = TRUE;
result = getValue(ExpTree, flag, );
return flag;
} /**
* @function 计算合法表达式的值,计算方式采用分数形式的计算
* @param ExpTree: 完整的表达式树
flag: 实时检测表达式是否合法的标记值
model: 控制台是否打印计算过程的标记值; 0:不打印计算过程, 1:打印
*
* @return result: 当前表达式的计算结果
*/
NumInfo getValue(BiTree ExpTree, int&flag, int model)
{
if(ExpTree->flag == )
return ExpTree->value.numForm;
else {
NumInfo opNum1 = getValue(ExpTree->lchild, flag, model);
NumInfo opNum2 = getValue(ExpTree->rchild, flag, model);
NumInfo result; //通分,获得两个操作数分母的最小公倍数
int LCM = findLCM(opNum1.denominator, opNum2.denominator); switch(ExpTree->value.opSym)
{
case '+' : result.denominator = LCM;
result.numerator = opNum1.numerator*(LCM / opNum1.denominator) + opNum2.numerator*(LCM / opNum2.denominator);
break; case '-' : result.denominator = LCM;
result.numerator = opNum1.numerator*(LCM / opNum1.denominator) - opNum2.numerator*(LCM / opNum2.denominator);
break; case '*' :
result.denominator = opNum1.denominator * opNum2.denominator;
result.numerator = opNum1.numerator * opNum2.numerator;
break; case '/' :
if(opNum2.numerator == )
flag = FALSE;
else {
result.denominator = opNum1.denominator * opNum2.numerator;
result.numerator = opNum1.numerator * opNum2.denominator;
}
break;
default: ;
} if(flag == TRUE)
{
result = simplify(result);
if(model == )
printf("(%d/%d) %c (%d/%d) = %d/%d\n", opNum1.numerator, opNum1.denominator, ExpTree->value.opSym,
opNum2.numerator, opNum2.denominator, result.numerator, result.denominator);
}
else {
result.numerator = ;
result.denominator = ;
}
return result;
}
} /**
* @function 寻找最大公约数,辗转相除法
* @param a: 参数1
b: 参数2
*
* @return 最大公约数
*/
int findGCD(int a, int b)
{
if(a == )
return b;
return findGCD(b%a, a);
} /**
* @function 寻找最小公倍数,两数积除以最大公约数
* @param a: 参数1
b: 参数2
*
* @return 最小公倍数
*/
int findLCM(int a, int b)
{
return (a*b)/findGCD(a, b);
} /**
* @function 化简分子和分母
* @param numForm: 一个用分子/分母表示的数
*
* @return afterSim: 化简后的分子/分母表示的数
*/
NumInfo simplify(NumInfo numForm)
{
NumInfo afterSim;
if(numForm.numerator == )
{
afterSim.numerator = ;
afterSim.denominator = ;
return afterSim;
} //寻找最大公因数
int GCD = findGCD(numForm.denominator, abs(numForm.numerator));
afterSim.numerator = numForm.numerator / GCD;
afterSim.denominator = numForm.denominator / GCD;
return afterSim;
} /**
* @function 检测当前生成的表达式是否与前面的题目重复
* @param ExpArr: 表达式数组
ExpTree: 当前表达式树
ExpIndex: 表达式数组的最后一个元素的索引
*
* @return flag: 当前表达式是否与前面题目重复的标记值 FALSE:不重复, TRUE:重复
*/
Status isRepetitive(ExpInfoPtr ExpArr, ExpInfo tempExpInfo, int ExpIndex)
{
int flag = FALSE;
int i;
int tempExpInfoOpNumCount = tempExpInfo.symCount+; for(i = ; i < ExpIndex; i++)
{
ExpInfo temp = ExpArr[i];
//1、比较表达式运算符数量
if( tempExpInfo.symCount == temp.symCount )
//2、比较表达式结果,分子分母对应相等
if( tempExpInfo.result.denominator == temp.result.denominator && tempExpInfo.result.numerator == temp.result.numerator)
//3、比较表达式的运算符数组
if( symArrCmp(tempExpInfo.symArr, temp.symArr, tempExpInfo.symCount) )
//4、比较表达式的操作数数组
if( opNumArrCmp(tempExpInfo.opNumArr, temp.opNumArr, tempExpInfoOpNumCount) )
//5、比较表达式树的结构
if( ExpTreeCmp(tempExpInfo.ExpTree, temp.ExpTree) )
{
flag = TRUE;
break;
}
}
return flag;
} /**
* @function 检测表达式1的树结构和表达式2是否有重复关系
* @param T1: 表达式树1
T2: 表达式树2
*
* @return flag: 表达式1和表达式2是否有重复关系的标记值, FLASE:不重复, TRUE:重复
*/
Status ExpTreeCmp(BiTree T1, BiTree T2)
{
if(T1->flag == T2->flag){ //结点类型相同
if(T1->flag == ){ //都是操作数结点
if(T1->value.numForm.numerator== && T2->value.numForm.numerator==)
return TRUE;
else if(T1->value.numForm.numerator == T2->value.numForm.numerator
&& T1->value.numForm.denominator == T2->value.numForm.denominator)
return TRUE;
else
return FALSE;
}
else if(T1->flag == ){ //都是操作符结点
if(T1->value.opSym != T2->value.opSym)
return FALSE;
// 操作符相同才递归比较
else{
if(T1->value.opSym == '*' || T1->value.opSym == '+'){
int flag1, flag2;
flag1 = ExpTreeCmp(T1->lchild, T2->lchild) && ExpTreeCmp(T1->rchild, T2->rchild); //比较左左和右右的结果
flag2 = ExpTreeCmp(T1->lchild, T2->rchild) && ExpTreeCmp(T1->rchild, T2->lchild); // 比较左右和右左的结果
return flag1 || flag2; //其中某一种相同则相同
}
else
return ExpTreeCmp(T1->lchild, T2->lchild) && ExpTreeCmp(T1->rchild, T2->rchild);
}
}
}
else
return FALSE; } /**
* @function 检测运算符数组1和数组2是否有重复关系
* @param arr1: 运算符数组1
arr2: 运算符数组2
length: 数组长度
*
* @return flag: 运算符数组1和数组2是否有重复关系的标记值, FALSE:不重复, TRUE:重复
*/
Status symArrCmp(char* arr1, char* arr2, int length)
{
//由于是乱序的数组比较,采用辅助标记数组的方式
int* flagArr = (int*) malloc(length * sizeof(int));
if(!flagArr)
return FALSE; int i, j;
for(i = ; i < length; i++)
flagArr[i] = ; for(i = ; i < length; i++)
{
for(j = ; j < length; j++)
if(arr1[i] == arr2[j] && flagArr[j] == )
{
flagArr[j] = ;
break;
}
} for(i = ; i < length; i++)
if(flagArr[i] != )
return FALSE; return TRUE;
} /**
* @function 检测操作数数组1和数组2是否有重复关系
* @param arr1: 操作数数组1
arr2: 操作数数组2
length: 数组长度
*
* @return flag: 操作数数组1和数组2是否有重复关系的标记值, FALSE:不重复, TRUE:重复
*/
Status opNumArrCmp(NumInfo* arr1, NumInfo* arr2, int length)
{
//由于是乱序的数组比较,采用辅助标记数组的方式
int* flagArr = (int*) malloc(length * sizeof(int));
if(!flagArr)
return FALSE; int i, j;
for(i = ; i < length; i++)
flagArr[i] = ; for(i = ; i < length; i++)
{
for(j = ; j < length; j++)
if(arr1[i].numerator == arr2[j].numerator &&
arr1[i].denominator == arr2[j].denominator && flagArr[j] == )
{
flagArr[j] = ;
break;
}
} for(i = ; i < length; i++)
if(flagArr[i] != )
return FALSE; return TRUE;
} /**
* @function 把当前表达式的基本信息存入表达式数组中
* @param ExpArr: 表达式数组
ExpInfo: 当前表达式的基本信息 * @return void
*/
void addInExpArr(ExpInfoPtr ExpArr, ExpInfo tempExpInfo, int ExpIndex)
{
ExpArr[ExpIndex].ExpTree = tempExpInfo.ExpTree;
ExpArr[ExpIndex].symCount = tempExpInfo.symCount;
ExpArr[ExpIndex].symArr = tempExpInfo.symArr;
ExpArr[ExpIndex].opNumArr = tempExpInfo.opNumArr;
ExpArr[ExpIndex].result = tempExpInfo.result;
} /**
* @function 获取当前表达式的操作数,并记录在一个数组中
* @param ExpTree: 表达式树
opNumArr: 操作数数组指针
opNumArrIndex: 操作数数组索引
*
* @return void
*/
void getOpNumArr(BiTree ExpTree, NumInfo* opNumArr, int& opNumArrIndex)
{
if(ExpTree->flag == )
opNumArr[opNumArrIndex++] = ExpTree->value.numForm; else{
getOpNumArr(ExpTree->lchild, opNumArr, opNumArrIndex);
getOpNumArr(ExpTree->rchild, opNumArr, opNumArrIndex);
}
}
下面是writeExpAndAnswer.cpp的代码:
#include "Myapp.h"
/**
* @function 比较运算符优先级
* @param 运算符c1, c2
*
* @return Bool值
*/
Status Priority_com(char c1,char c2){
if((c1 == '+' || c1 == '-' || c1 == '*' || c1 == '/') && (c2 == '+' || c2 == '-' || c2 == '*' || c2 == '/'))
{
if(c1 == '*' || c1 == '/')
{
if(c2 == '+' || c2 == '-')
return TRUE;
else
return FALSE;
}
else
return FALSE;
}
else
return FALSE;
} /**
* @function 将当前表达式写入题目文件
* @param T: 表达式树
file: 题目文件指针
*
* @return void
*/
void writeExp(BiTree T, FILE* file)
{
if(!T)
return; if(T->lchild && T->lchild->flag == ){ //如果根节点与左子树的根节点是运算符
if(Priority_com(T->value.opSym, T->lchild->value.opSym)){
printf("( ");
fprintf(file,"( ");
writeExp(T->lchild, file);
printf(") ");
fprintf(file,") ");
}
else
writeExp(T->lchild, file);
}
else
writeExp(T->lchild, file); if(T->flag == ){
printf("%c ",T->value.opSym);
fprintf(file,"%c ",T->value.opSym);
}
else {
int numerator = T->value.numForm.numerator;
int denominator = T->value.numForm.denominator; if(numerator % denominator == ){ //自然数
printf("%d ",numerator/denominator);
fprintf(file,"%d ",numerator/denominator);
}
else{
if(numerator < denominator){ //真分数
printf("%d/%d ",numerator,denominator);
fprintf(file,"%d/%d ",numerator,denominator);
}
else{ //带分数
printf("%d\'%d/%d ",numerator/denominator,numerator%denominator,denominator);
fprintf(file,"%d\'%d/%d ",numerator/denominator,numerator%denominator,denominator);
}
}
} if(T->rchild && T->rchild->flag == ){ //如果根节点与右子树的根节点是运算符
printf("( ");
fprintf(file,"( ");
writeExp(T->rchild, file);
printf(") ");
fprintf(file,") ");
}
else
writeExp(T->rchild, file);
} /**
* @function 将当前表达式写入答案文件
* @param T: 表达式树
file: 答案文件指针
*
* @return void
*/
void writeAnswer(NumInfo result, FILE* answerFile)
{
int numerator = result.numerator;
int denominator = result.denominator;
if(numerator % denominator == ){ //自然数
printf("%d\n",numerator/denominator);
fprintf(answerFile,"%d\n",numerator/denominator);
}
else{
if(abs(numerator) < abs(denominator)){ //真分数
printf("%d/%d\n",numerator,denominator);
fprintf(answerFile,"%d/%d\n",numerator,denominator);
}
else{ //带分数
printf("%d\'%d/%d\n",numerator/denominator,abs(numerator)%denominator,denominator);
fprintf(answerFile,"%d\'%d/%d\n",numerator/denominator,abs(numerator)%denominator,denominator);
}
}
}
测试运行
项目小结
这次项目是结对项目,需要两个人的合作,一个人编码,一个人设计复审,角色轮流交换。每个阶段都有不同的责任,项目采用功能分块,由简入繁的过程开发,还记得最初的版本,只有一个main函数,两个调用的函数,没用命令行,参数固定,再到后面参数改变,不断测试,有时在测试中找到的是之前写的函数的bug,属实尴尬,不过通过不断的代码复审,代码阅读,时常灵光一闪,这段删掉,这段补上,if-else丢得只剩一个,逻辑简化,效果更佳。也有讨论到入夜已深,手机常亮,想法迸发,落魄发现,方案搞笑,打回重想。但是,个中滋味,回味如甘,编码乐趣,在于敲下。
自动生成四则运算题目(C语言)的更多相关文章
- 结对项目 实现自动生成四则运算题目的程序 (C++)
本次作业由 陈余 与 郭奕材 结对完成 零.github地址: https://github.com/King-Authur/-Automatically-generate-four-arithmet ...
- 20194651—自动生成四则运算题第一版报告chris
1.需求分析: (1)自动生成四则运算算式(+ - * /),或两则运算(+ -). (2)剔除重复算式. (3)题目数量可定制. (4)相关参数可控制. (5)生成的运算题存储到外部文件中. 2 ...
- C语言编程—自动生成四则运算升级版
#include<stdio.h> #include<time.h> struct fenshu { int fenzi; int fenmu; }Fenshu[]; int ...
- java实现自动生成四则运算
Github项目链接:https://github.com/shoulder01/Fouroperation.git 一.项目相关要求 1. 使用 -n 参数控制生成题目的个数(实现) 2.使用 -r ...
- C语言#自动生成四则运算的编程
#include <iostream> #include <stdio.h> #include <stdlib.h> #include <time.h> ...
- 用C语言编程自动生成四则运算
#include<stdio.h>#include<stdlib.h>#include <time.h>#define N 30main(){ int a,b,k, ...
- 作业二:个人编程项目——编写一个能自动生成小学四则运算题目的程序
1. 编写一个能自动生成小学四则运算题目的程序.(10分) 基本要求: 除了整数以外,还能支持真分数的四则运算. 对实现的功能进行描述,并且对实现结果要求截图. 本题发一篇随笔,内容包括: 题 ...
- 个人作业1——四则运算题目生成程序(基于C++)
题目描述: 从<构建之法>第一章的 "程序" 例子出发,像阿超那样,花二十分钟写一个能自动生成小学四则运算题目的命令行 "软件",满足以下需求: 1 ...
- (幼儿园毕业)Javascript小学级随机生成四则运算
软件工程第二次结对作业四则运算自动生成器网页版 一.题目要求 本次作业要求两个人合作完成,驾驶员和导航员角色自定,鼓励大家在工作期间角色随时互换,这里会布置两个题目,请各组成员根据自己的爱好任选一题. ...
随机推荐
- (转)C代码优化方案
C代码优化方案 原文地址:http://www.uml.org.cn/c++/200811103.asp 目录 C代码优化方案 1.选择合适的算法和数据结构 2.使用尽量小的数据类型 3.减少运算的强 ...
- POI小demo
使用poi需要先下载相关jar包(http://download.csdn.net/detail/wangkunisok/9454545) poi-3.14-20160307.jar poi-ooxm ...
- Python3学习之路~10.3 论事件驱动与异步IO
论事件驱动----详见:https://www.cnblogs.com/alex3714/articles/5248247.html Select\Poll\Epoll异步IO----详见:http: ...
- FFmpeg SDK for iOS
FFmpeg是一套可以用来记录.转换数字音频.视频,并能将其转化为流的跨平台开源计算机程序. 很多平台视频播放器都是使用FFmpeg来开发的,FFmpeg官方并没有为各个平台提供编译好的SDK,所以使 ...
- 安装SQL Server 2008R2 报错“此计算机上安装了 Microsoft Visual Studio 2008 的早期版本”解决方法
安装SQL Server 2008 R2报错“此计算机上安装了 Microsoft Visual Studio 2008 的早期版本,请在安装 SQL Server 2008 前将 VS2008 升级 ...
- 测试工程师需要了解的shell变量知识
欢迎访问个人博客 什么是变量 本地变量:手动定义的,在当前系统的某个环境下才能生效,作用范围小 普通变量: 单引号:原字符输出,变量名='变量值' ➜ shell name='tom' ➜ shell ...
- payload分离免杀
shellcode loader 借助第三方加载器,将shellcode加载到内存中来执行. https://github.com/clinicallyinane/shellcode_launcher ...
- BIT-Count of Range Sum
2019-12-17 18:56:56 问题描述: 问题求解: 本题个人感觉还是很有难度的,主要的难点在于如何将题目转化为bit计数问题. 首先构建一个presum数组,这个没有问题. 需要对于任意一 ...
- MATLAB GUI 预约程序
因为一起奇怪的原因,要做一个预约程序.初衷是能够完成注册.登陆.预约.查看个人信息等. 原本想用Java写的,又由于一些特殊原因耽搁了,导致最后只有一个晚上的时间,匆匆忙忙到最后就用MATLAB GU ...
- .NET Core技术研究-配置读取
升级ASP.NET Core后,配置的读取是第一个要明确的技术.原先的App.Config.Web.Config.自定义Config在ASP.NET Core中如何正常使用.有必要好好总结整理一下,相 ...