四则运算题目生成

  • Github项目地址
  • 题目要求
  • 组内分工
  • 解题思路描述
  • 算法设计
  • 数据结构设计
  • 流程图
  • 核心函数
  • 部分源码
  • 测试

  

Github项目地址

链接: Github/Sheep11/calculator
写者id为:PanQuixote
  

题目要求

  写一个能自动生成小学四则运算题目的软件,满足以下基本需求:

  1. 一次可以出一千道不相同的题目,把题目写入一个文件中。此处“不相同”的定义是:两道题目不能通过有限次交换 + 和 * 左右的算术表达式变换为同一个题目。
  2. 题目最多10个运算符,括号数量不限。
  3. 除了整数以外,要支持真分数的四则运算。
  4. 能让用户输入答案,并且判断答案对错。
  5. 支持乘方运算,乘方用 ^ 或者 ** 表示,由用户选择。

  
  此外还有4个扩展方向,本小组选择的是第1个方向,此扩展方向要求如下:

  1. 把程序变成一个 Windows/Mac/Linux 电脑图形界面程序。
  2. 增加倒计时功能,每个题目要求在20秒内完成,完不成则记得分为0分并进入下一题。
  3. 增加历史记录功能,把做题的成绩记录下来,并且可以展示。
      

组内分工

  写者负责实现基本需求,搭档负责实现扩展需求。
  

解题思路描述

  1. 生成题目:先生成一串合法的包含运算符和括号的string,再向其中添加数字。
  2. 求解题目:将题目转化成后缀表达式,再计算后缀表达式。
  3. 检查题目是否重复:将当前题目与之前的题目比较。先比较计算结果是否相同,如果不同则肯定不重复。如果相同则比较其表达式二叉树,如果两个表达式二叉树能通过交换 + 和 * 节点的子节点而转化为同样的树,则两个表达式是相同的。检查过程如下:将题目转化为一个表达式二叉树。将此题目的答案,与之前保存的答案比较,如果不同,则不重复;如果相同,将它的二叉树与之前的二叉树比较,如果不同,则不重复,否则重复。
      

算法设计

   生成题目过程如下:

  1. 生成一个只含运算符和括号的string类。由于无法直接随机生成运算符和括号,于是采用间接方法。具体方法如下:生成一串数字,每个数字代表不同的符号,1代表 “+”,2代表 “-”,3代表 “*”,4代表 “/”,5代表 “^”,6代表 “(”,7代表 “)”。再将数字转化为符号。注意,此时的式子不一定是合法的,因为可能出现括号不匹配,或者两个不同的括号中间没有运算符的情况。
  2. 将生成的string规范化。采用的方法是,如果两个不同括号中间没有运算符,则随机添加一个运算符到中间;如果“)”没有与之匹配的“(”,则将其删除;如果“(”没有与之匹配的“)”,则增加一个“)”到串的末尾。
  3. 向string中运算符的前后添加随机数字。

   求解题目过程如下:

  1. 将题目转化为后缀表达式。转化为后缀表达式的方法如下:(1)创建一个栈,用于存储运算符。(2)从左到右遍历表达式,如果遇到数字,直接输出;如果遇到左括号,将左括号压入运算符栈(3)如果遇到右括号,运算符栈不断出栈直到被弹出的为左括号。(4)如果遇到运算符:如果运算符栈为空,直接将其入栈;如果不为空,不断出栈直到当前运算符的算术级高于栈顶运算符的算术级。(5)表达式遍历完后,将操作符栈的所有元素出栈并输出。(6)按顺序保存以上操作的输出,即为后缀表达式。
  2. 计算后缀表达式。计算方法如下:(1)创建一个数字栈。(2)从左到右遍历后缀表达式,如果遇到的为数字,将其入栈。(3)如果遇到运算符,从数字栈弹出一个数字作为右运算数,再弹出一个数字作为左运算数,计算运算结果,将结果入数字栈。(4)表达式遍历完后,数字栈中的唯一元素即为最终的计算结果。如果数字栈中不只一个元素,则说明表达式有误。(注:由于可能会有分数出现,计算时将整数看做是分母为1的分数来计算)

   检测题目是否重复的过程如下:

  1. 先比较当前式子和之前生成的式子的运算结果是否相同,如果不同,则当前式子不与之前的式子重复。如果相同,则要建立当前式子的表达式二叉树,与之前式子的表达式二叉树比较。建立表达式二叉树的过程如下:(1)创建一个二叉树节点栈。(2)将式子的后缀表达式中的数字、运算符都转化为二叉树节点。得到一个二叉树节点串形式的后缀表达式。(3)从左到右遍历后缀表达式。如果遇到的为数字节点,将其入栈。(4)如果遇到的为运算符节点,栈顶节点出栈,作为当前运算符的右节点。此时的栈顶节点再出栈,作为当前运算符的左节点。当前运算符入栈。

  2. 比较当前式子的表达式二叉树和之前式子的表达式二叉树。比较函数int compare_tree(bi_tree a, bi_tree b)的实现过程如下:(1)如果a为空或者b为空,返回1。(2)如果a、b其中一个为空,返回0。(3)如果a、b全为非空,如果a、b的值不同,返回0。(4)如果值相同,递归调用int compare_tree(a->left_child, b->left_child),如果返回值为1,再递归调用int compare_tree(a->right_child, b->right_child);如果返回值为0且a、b的值为 + 或 *,交换a的左右节点,递归调用int compare_tree(a->left_child, b->left_child),如果返回值为1,再递归调用int compare_tree(a->right_child, b->right_child)。最终实现的功能是,将两棵表达式树的根节点作为a、b传入函数,如果两棵树能通过有限次交换加号和乘号节点的左右节点转化为同一棵树,则认为两棵树相同,返回1,否则返回0。
      

数据结构设计

   由于转化为后缀表达式时,表达式中既有数字又有符号,因而设计了一个类,名字为word,既可以存储数字,又可以存储符号。用于保存表达式中的元素。word的属性如下:

class word
{int type;//type = 0则表示数字,等于1表示操作符,//等于-1表示错误的结果(未初始化、出现除以0、表达式错误)int num;//numerator分子int de;//denominator分母int oper;//操作符
}

  
   此外,还要设计二叉树的节点。此节点中既要保存节点数据,还要有指向其左节点和右节点的指针。节点类的属性如下:

class bi_tree
{word value;//当前节点的数据bi_tree *l_child;//指向左节点的指针bi_tree *r_child;//指向右节点的指针
}

  
   在生成题目、计算出答案之后要将这些数据传给前端,因而要设计一个类用于保存一道题的题目和答案。此类的属性如下:

class formula
{int id;//题目编号string problem;//题目string answer;//答案
}

  
   设计了三个工具类,分别是计算工具类calculate_tool,转化工具类translate_tool,生成工具类generate_tool。三个类中的主要方法如下:

class calculate_tool
{
public://返回后缀表达式suffix的计算结果,如果表达式不正确,返回的result.type = -1word calculate_suffix(std::queue<word> suffix);
};class translate_tool
{
public://将exp转化为后缀表达式,存在队列里返回queue<word> translate_into_suffix(const string exp);
};class generate_tool
{
public://返回一个容器,里面存储着N个表达式及答案vector<formula> generate_exp(int N = 1000, int max_number = 10, int max_oper_sum = 10, int show_way = 0);
};

  
   设计了一个工具类,供前端使用。前端只需创建一个此类的对象,即可使用 生成题目、检测答案是否正确等功能。此类的定义如下:

class generator
{
private:vector<formula> formula_vector;//存储题目和答案的容器int sum;//存储的数量int index;//没有使用过的第一个题目和答案的编号(编号数值等于下标+1)
public:generator();//构造函数//输出式子到文件path中,默认为problem_file.txtvoid output_into_file(string exp, string path = "problem_file.txt");//获取一个式子及答案,返回值为formula对象,当容器中没有未使用过的题目时,会自动生成新的题目//默认显示乘方为“^”,如果需要切换为“**”,传入整型参数1formula get_formula(int show_way = 0);//检查答案是否正确。id为题号,u_answer为用户的答案int check_answer(int id, string u_answer);
};

类图如下:

  

流程图


  

核心函数

  
表达式转化为后缀表达式

//将exp转化为后缀表达式,存在队列里返回
queue<word> translate_tool::translate_into_suffix(const string exp)
{queue<class word>suffix;//存储后缀表达式的队列stack<int>oper_stack;//暂时存储操作符的栈for (int i = 0; i < exp.size(); i++){int type = check_type(exp[i]);//当前字符的类型if (type == 0)continue;if (type == -2){while (suffix.size() != 0)suffix.pop();return suffix;}if (type == -1)//如果为数字{int num = exp[i] - '0';for (i = i + 1; i < exp.size(); i++){if (check_type(exp[i]) < 0)num = num * 10 + (exp[i] - '0');else{i--;break;}}word num_t(0, num);suffix.push(num_t);//加入后缀式队列continue;}else//如果为符号{//如果为括号if (type == L_BRACKET)//如果是左括号,直接入操作符栈{oper_stack.push(L_BRACKET);continue;}else if (type == R_BRACKET)//如果是右括号,不断将操作符栈的栈顶弹出到后缀式队列,直到遇到左括号(最后将左括号也出栈但不加入后缀式队列){while (oper_stack.top() != L_BRACKET){word tem_oper(1, oper_stack.top());suffix.push(tem_oper);oper_stack.pop();}oper_stack.pop();continue;}//如果为运算符if (oper_stack.empty())//如果操作符栈为空则直接入栈{oper_stack.push(type);}else//如果操作符栈不为空,当当前操作符不高于栈顶操作符的优先级时,不断出栈。最后将当前操作符入栈{//操作符栈非空且当前操作符的优先级不高于操作符栈顶的操作符while ((!oper_stack.empty()) && cmp_priority(exp[i], change_oper(oper_stack.top())) <= 0){word tem_oper(1, oper_stack.top());suffix.push(tem_oper);oper_stack.pop();}oper_stack.push(type);//当前操作符入后缀式队列}}}//将操作符栈中剩余的操作符全部弹出到后缀式队列while (!oper_stack.empty()){word tem_oper(1, oper_stack.top());suffix.push(tem_oper);oper_stack.pop();}return suffix;
}

  
计算后缀表达式

//返回后缀表达式suffix的计算结果,如果表达式不正确,返回的result.type = -1
word calculate_tool::calculate_suffix(string exp)
{translate_tool T_tool;queue<word> suffix = T_tool.translate_into_suffix(exp);word result;class stack<word> num_stack;//数字栈//后缀队列非空时while (!suffix.empty()){word t_word;//临时存储从队列中弹出的元素t_word = suffix.front();suffix.pop();if (t_word.type == 0)//如果为数字{num_stack.push(t_word);//入数字栈}else//如果为操作符{//从数字栈中弹出两个数字,计算结果,将结果入数字栈word b = num_stack.top();num_stack.pop();word a = num_stack.top();num_stack.pop();word c = calculate(a, t_word, b);if (c.type == -1)//如果计算结果出现错误{result.init(-1, 0, 0);return result;}elsenum_stack.push(c);}}//如果最终数字栈里只有一个数字,说明表达式正确,否则错误if (num_stack.size() == 1)return num_stack.top();else{result.init(-1, 0, 0);return result;}}

  
转化后缀表达式为二叉树

//后缀表达式转换为二叉树
bi_tree generate_tool::translate_into_bi_tree(class queue<word> suffix)
{stack<bi_tree> t_stack;//临时栈//后缀队列非空时while (!suffix.empty()){word t_word;//临时存储从队列中弹出的元素t_word = suffix.front();suffix.pop();//bi_tree *a = new bi_tree();if (t_word.type == 0)//如果为数字{bi_tree t_tree(t_word);t_stack.push(t_tree);}else//如果为操作符{bi_tree* b = new bi_tree();*b = t_stack.top();t_stack.pop();bi_tree* a = new bi_tree();*a = t_stack.top();t_stack.pop();bi_tree* c = new bi_tree();c->init(t_word, a, b);t_stack.push(*c);}}bi_tree return_value = t_stack.top();return return_value;
}

  
  
比较两棵二叉树是否相同

//判断a,b的子树是否有交叉比较的必要(即是否a、b均为加号或乘号),如果有,返回1
int generate_tool::swap_flag(bi_tree a, bi_tree b)
{//如果a,b存储的均为+或者*if ((a.value.oper == PLUS && b.value.oper == PLUS) || (a.value.oper == MULTI && b.value.oper == MULTI))return 1;elsereturn 0;
}//如果两棵树相同返回1,否则返回0
int generate_tool::compare_tree(bi_tree *a, bi_tree *b)
{if (a == NULL && b == NULL)//如果a b均为叶子节点return 1;if ((a != NULL && b == NULL) || (a == NULL && b != NULL))//a b不全为叶子节点return 0;if (a && b){if (cmp_word(a->value, b->value))//如果当前节点相同{if (compare_tree(a->l_child, b->l_child))//比较a、b的左节点return compare_tree(a->r_child, b->r_child);//比较a、b的右节点else{//如果有交叉比较的必要,交叉比较a、b的左右节点if (swap_flag(*a, *b) && compare_tree(a->r_child, b->l_child))return compare_tree(a->l_child, b->r_child);}}}return 0;
};

  

部分源码

  
   生成题目工具类的部分方法

//生成N个表达式和答案,默认N为1000,存在栈中返回
//max_number为表达式中数字的最大值,默认为10
//max_oper_sum为表达式中最多含有的运算符的数量,默认为10
//show_way为显示乘方的模式,为0显示^,为1显示**,默认为0
vector<formula> generate_tool::generate_exp(int N, int max_number, int max_oper_sum, int show_way)
{vector<formula>  F_vector;//将要返回的容器if (N > MAX_SIZE)return F_vector;srand(time(NULL));string exp[MAX_SIZE];//表达式的字符串形式bi_tree tree[MAX_SIZE];//表达式的二叉树形式queue<word> suffix[MAX_SIZE];//后缀表达式word result[MAX_SIZE];//表达式的计算结果translate_tool T_tool;calculate_tool C_tool;generate_tool G_tool;int n = 0;//当前的式子编号while (n < N){G_tool.add_oper_into_exp(exp[n], max_oper_sum);//添加操作符G_tool.normalize_exp(exp[n]);//规范化G_tool.add_number_into_exp(exp[n], max_number);//添加数字suffix[n] = T_tool.translate_into_suffix(exp[n]);//转化为后缀表达式result[n] = C_tool.calculate_suffix(suffix[n]);//计算后缀表达式的值if (G_tool.restrict_result(exp[n], result[n]) == 0)//如果计算结果不符合要求,重新生成表达式{G_tool.clear_trail(exp[n], suffix[n], result[n], tree[n]);//清除记录continue;}else//结果符合要求,检测是否与之前生成的表达式重复{tree[n] = G_tool.translate_into_bi_tree(suffix[n]);//由后缀表达式生成二叉树int repeat_flag = G_tool.is_repeat(result, result[n], tree, tree[n], n);if (repeat_flag == 1)//重复{G_tool.clear_trail(exp[n], suffix[n], result[n], tree[n]);//清除记录continue;}}//如果show_way = 1,把表达式中的^换成**if (show_way == 1)G_tool.change_show_way(exp[n]);//表达式和答案存入容器中formula F;F.init(exp[n], result[n].str_word());F_vector.push_back(F);n++;}return F_vector;
}

  
   前端工具类的部分方法:

//获取一个式子及答案,返回值为formula对象,当容器中没有未使用过的题目时,会自动生成新的题目
//默认显示乘方为“^”,如果需要切换为“**”,传入整型参数1
formula generator::get_formula(int show_way)
{if (index > sum)//题目用尽,重新生成{generate_tool G_tool;vector<formula> tem_vector = G_tool.generate_exp(1000, 10, 10);//生成1000道题目formula_vector.insert(formula_vector.end(), tem_vector.begin(), tem_vector.end());//生成的题目加入容器中sum += 1000;}//给题目和答案进行编号formula_vector[index-1].id = index;//如果显示模式为1if (show_way == 1){generate_tool G_tool;G_tool.change_show_way(formula_vector[index-1].problem);//改变题目的显示模式}formula return_value;return_value = formula_vector[index-1];//返回值index++;//输出到文件output_into_file(return_value.problem);return return_value;
}
//检查答案是否正确。id为题号,u_answer为用户的答案,正确返回1,错误返回0
int generator::check_answer(int id, string u_answer)
{if (u_answer == formula_vector[id - 1].answer)return 1;//将分母的负号挪到分子上,再进行比较for (int i = 0; i < u_answer.size(); i++){if (u_answer[i] == '/'){if (u_answer[i + 1] == '-'){u_answer.erase(i + 1, 1);u_answer.insert(0, "-");if (u_answer == formula_vector[id - 1].answer)return 1;elsereturn 0;}}}return 0;
}

  

测试

使用的测试代码如下

#include "libraryfile_and_define.h"
#include "class.h"int main()
{generator G;//工具类//生成5道题,输出,乘方显示为^for (int i = 1; i <= 5; i++){formula F = G.get_formula();cout << "题号:" << F.id << endl;cout << "题目:" << F.problem << endl;cout << "答案:" << F.answer << endl << endl;}//生成5道题,输出,乘方显示为**for (int i = 1; i <= 5; i++){formula F = G.get_formula(1);cout << "题号:" << F.id << endl;cout << "题目:" << F.problem << endl;cout << "答案:" << F.answer << endl << endl;}system("pause");
}

  
结果如下:

  
可以看出,实现了需求。

软件工程结队项目——1120161755相关推荐

  1. 软件工程结队项目——智能点餐系统典型用户及用户场景分析

    一.典型用户分析:一个典型用户描述了一组用户的典型技巧.能力.需要.想法.工作习惯和工作环境. 1.买家典型用户分析: 名字 小郭(石家庄铁道大学交1202-5班) 性别.年龄 男,22岁 联系方式 ...

  2. 小组结队项目-四则运算

    小队成员:潘恋军-1120161955 注:由于种种因素,我最后一个人组队,所以小组只有一个成员. 结队项目-四则运算 GitHub地址:https://github.com/bt-112016195 ...

  3. 软件工程个人项目——买书的最低价格

    软件工程个人项目--买书的最低价格 题目要求: 书店针对<哈利波特>系列书籍进行促销活动,一共5卷,用编号0.1.2.3.4表示,单独一卷售价8元, 具体折扣如下所示: 本数        ...

  4. 结队项目之需求分析与原型设计

    结队项目之需求分析与原型设计 结对者:3011 卢凯欣    3034 戚景晓 一.需求分析(NABCD模型) 1.N(Need,需求) 游戏玩家可以以游客的身份游览游戏界面. 玩家可以注册登录,在玩 ...

  5. 大三软件工程小项目-小技术集合总结

    大三软件工程小项目-小技术集 此篇文章是给出了此小项目用到的技术的总结: 方便自己有忘记的知识点后方便查阅. 也方便有需要的博友看. 下面是各个计算的链接 大家点击了进能进去 大三软件工程小项目-小技 ...

  6. 现代软件工程团队项目贝塔阶段_大规模测试结果_2018.02.08

    现代软件工程团队项目贝塔阶段_大规模测试结果_2018.02.08 经过课程全班同学测试后,将收集到的所有BUG和建议汇总如下 BUG按照状态.严重程度.优先级进行了基本的分类 目前打算的BUG修改顺 ...

  7. BIT软件工程个人项目——数独sudoku

    BIT软件工程个人项目--数独sudoku 目录: (点击可页内跳转) 1. 项目地址 2. PSP表格 3. 解题思路描述 --3.1初期思考 --3.2数独终局生成 --3.3求解数独 4. 设计 ...

  8. JavaWeb项目-快递代领-需求分析(二)-软件工程-小组项目

    快递带领-需求分析 1.项目简介 1.1项目背景 快递又称速递或快运,是指物流企业(含货运代理) 通过自身的独立网络或以联营合作(即联网)的方式,将用户委托的文件或包裹,快捷而安全地从发件人送达收件人 ...

  9. 软件工程小组项目——单词计数

    软件工程小组项目--单词计数 项目地址 PSP表格 解题思路 实现过程 1.字符数 2.单词数 3.行数 4.各段行数统计 5.对子目录文件进行操作 6.图形界面 性能优化 实验总结 项目地址 Git ...

  10. 201671010434王雯涵--实验二 软件工程个人项目

    作业要求:实验二 软件工程个人项目 本项目完成的代码 1.软件定义 ※程序可读入任意英文文本文件,该文件中英文词数大于等于1个,统计该文本所有单词数量及词频数,并能将单词及词频数按字典顺序输出到文件r ...

最新文章

  1. 火狐的萤火虫JavaScript,HTML,CSS调试捕获器
  2. java 锁_Java 锁之我见
  3. 计算机网络基础中职学校,浅谈中职学校计算机网络基础教学
  4. 七十四、Python | Leetcode数字系列(下篇)
  5. linux笔记之 开机服务启动的控制,系统日志的查看,防火墙的关闭
  6. 人脸检测源码facedetection
  7. 本地上传图片无法预览
  8. linux分配内核,linux 内核分配算法
  9. mysql innodb_file_per_table=1_mysql-5.7 innodb_file_per_table 详解
  10. 最容易被你忽略的Mac神级功能!Mac访达里一切皆可标记
  11. 查询Mysql的数据架构信息研究
  12. JUnit-三角形判断测试
  13. 工业以太网交换机常见的几种故障类型及分析排查方法
  14. js鼠标点击位置 弹出层由中心向四周缓慢扩大
  15. PDF编辑器:Adobe Acrobat XI Pro
  16. 数据治理系列文章:(7)数据安全
  17. Windows驱动(创建驱动符号链接)
  18. IPsec ACL隧道模式的路由设置
  19. 如何用u盘做系统盘?
  20. 含有咪唑盐和透明质酸/L赖氨酸改性透明质酸衍生物/基于透明质酸聚谷氨酸水凝胶的研究

热门文章

  1. 《屌丝日记》系列-开篇
  2. 伯尔尼大学计算机专业,2021年QS世界大学学科排名发布,瑞士大学表现出色!
  3. 较为全面的解析对形式主义的批判:狗屁不通文章生成器原理
  4. 微信小程序与ThinkPHP
  5. 阅读 :Spatio-temporal access methods: a survey (2010 - 2017)
  6. 【一图流思维导图】团队管理 项目管理
  7. 软件测试工程师的职业发展方向,别迷茫了,振作起来
  8. 荣耀10正式登陆印尼
  9. 准双向口与双向口的差别
  10. 新FSB主席:加密货币可以挑战“任何财务框架”