本次作业由 陈余 与 郭奕材 结对完成

零、github地址:

https://github.com/King-Authur/-Automatically-generate-four-arithmetic-problems


一、项目的相关要求

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



项目需求

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


二、遇到的困难和解决方法

(一)如何进行结对编程

在项目开始前我们进行了时长为一个多小时的讨论,理清了思路并对项目进行了整体设计和规划,随后我们分配好各自要负责的模块,并写好了接口文件进行项目的开发。

在编程的过程中,我们遇到有疑惑或者觉得思路上有出入的地方也会在微信上及时进行沟通,保证了项目的正常推进。

完成代码编写之后,我们先各自检查了自己的代码、测试模块,随后将代码整合起来,发送给对方让对方进行代码复审。在实际操作中,我们都为对方发现了许多的bug。

(二)如何存储数据

为了能够较为简易的实现对分数的存储和计算等操作,我们经过讨论,决定选择用结构体来存储,并对结构体的部分运算符进行重载。


三、关键代码 和 设计说明

整体设计

数据的定义

typedef struct variable
{
int num_or_Symbol; //0是数字1是符号
int Symbol = -1; // + - * % ( ) 分别表示为 0 1 2 3 4 5
int numer; //如果是数字此为分子
int Den = 1; //如果是数字此为分母
int num; //如果是数字此为分数前的系数
bool operator == (variable c){
return num_or_Symbol == c.num_or_Symbol && Symbol == c.Symbol && numer == c.numer && Den == c.Den && num == c.num;
}
}var;

主要的几个部分如下

//生成表达式函数
Status Create(var** exp, int size, int *length);
//计算表达式函数
Status Calculation(var* exp, int size, var* result, int length);
//中缀表达式转后缀表达式
Status Infix_to_Postfix(var* p, int size, var* Postfix, int length, int& postLen);
//判断两个问题是否等价
Status is_question_same(var* Question, int lenQuest, var* newQuestion, int lenNewQuest, int size);
//m指令的执行
void M_instructions(var** expression, int amount, int size, var* result);
//判断对错
void Correction(int* save, char* answerfile, char* exercisefile);

关键代码

创建题目

Status Create(var** exp, int size, int* length)
{
var* expre;
int mark_num = random(1, 4);//计算符个数
int pre = 0;//前括号在第pre个数字前
int aft = 0;//后括号在第aft个数字后
int judge = 0;//判断,0写入数字,1写入符号
int n = 0;
*length = mark_num + mark_num + 1;
n = 0;
if (mark_num > 1)//如果运算符有3个,则存在括号
{
pre = random(1, mark_num);
if(pre == 1)//不让括号括住整个式子
aft = random((pre + 1), (mark_num + 1));
else
aft = random((pre + 1), (mark_num + 2));
(*length) += 2;
expre = new var[*length + 1];
expre[pre * 2 - 2].num_or_Symbol = 1;
expre[pre * 2 - 2].Symbol = 4;
expre[aft * 2].num_or_Symbol = 1;
expre[aft * 2].Symbol = 5;
}
else
{
expre = new var[*length + 1];
}
n = 0;
while (n < *length)
{
if (expre[n].Symbol < 4)
{
if (judge == 0)
{
expre[n].num_or_Symbol = 0;
expre[n].Den = random(2, size);
expre[n].numer = random(0, expre[n].Den);
expre[n].num = random(1, size);
judge = 1;
}
else
{
expre[n].num_or_Symbol = 1;
expre[n].Symbol = random(0, 4);
judge = 0;
}
}
n++;
}
*exp = expre;
return SUCCESS;
}

中缀表达式转后缀表达式

Status Infix_to_Postfix(var* p, int size, var* Postfix, int length, int& postLen)
{
//传入的postfix要记得为空
var stack[maxn];
int top = 0;
for (int i = 0; i < length; i++)
{
if (p[i].num_or_Symbol == 0)//是数字
{
Postfix[postLen++] = p[i];//放入输出串中
}
if (p[i].num_or_Symbol == 1 && p[i].Symbol == 4)//左括号
{
++top;
stack[top] = p[i];
}
while (p[i].num_or_Symbol == 1 && p[i].Symbol != 4 && p[i].Symbol != 5)
{
if (top == 0 || stack[top].Symbol == 4 || prio(p[i]) > prio(stack[top]))
{
++top;
stack[top] = p[i];
break;
}
else
{
Postfix[postLen++] = stack[top];
top--;
}
}
if (p[i].num_or_Symbol == 1 && p[i].Symbol == 5)//右括号
{
while (stack[top].Symbol != 4)
{
Postfix[postLen++] = stack[top];
top--;
}
top--;
}
}
while (top != 0)
{
Postfix[postLen++] = stack[top--];
}
return SUCCESS;
}

判断题目是否等价

Status is_problem_same(var* Question, int lenQuest, var* newQuestion, int lenNewQuest, int size)
{
var Postfix1[maxn], Postfix2[maxn];
var stack1[3][3], stack2[3][3];
int len1 = 0, len2 = 0, sta_size1 = 0, sta_size2 = 0; //获取后缀表达式
Infix_to_Postfix(Question, size, Postfix1, lenQuest , len1);
Infix_to_Postfix(newQuestion, size, Postfix2, lenNewQuest, len2); //获取子表达式
get_Subexpression(Postfix1, len1, stack1, sta_size1);
get_Subexpression(Postfix2, len2, stack2, sta_size2); bool flag;
for (int i = 0; i < sta_size1; i++)
{
flag = false;
for (int j = 0; j < sta_size2; j++)
{
//短式等价
if (cmp(stack1[i], stack2[j]))
{
flag = true;
stack2[j][2].Symbol = -1;//将表达式的运算符删掉
break;
}
}
if (!flag)//如果存在不一样的,返回not same
{
return ERROR;
}
}
return SUCCESS;
}

m操作

void M_instructions(var **expression, int amount, int size, var* result)
{
fstream answer;
answer.open(ANSWERFILE, ios::out | ios::app);
var results[maxn];//后缀表达式
int length;
int i = 0;
int j = 0;
int k = 0;
while (i < amount)
{
Create(&expression[i], size, &length);
result[i].Symbol = length;
if (Calculation(expression[i], size, results, length) == ERROR || results[0].num >= size || results[0].numer >= size || results[0].Den >= size)
{
continue;
}
result[i].Den = results[0].Den;
result[i].num = results[0].num;
result[i].numer = results[0].numer;
result[i].num_or_Symbol = 0;
result[i].Symbol = length;
j = 0;
while (j < i)
{
//结果一样,表达式可能一样
if (result[j].Den == result[i].Den && result[j].numer == result[i].numer && result[j].num == result[i].num)
{
if (is_question_same(expression[i], result[i].Symbol, expression[j], result[j].Symbol, size))
{
break;
}
}
j++;
}
if (i != j)
{
if(k ++ < 20)//连续20次重复答案表明给的size太小,而amount太大,表达式多样性不足
continue;
}
Visit(expression[i], length, i + 1); answer << i + 1 << ". ";
if (result[i].numer == 0)
{
answer << result[i].num;
}
else
{
if (result[i].num != 0)
{
answer << result[i].num;
answer << "`";
}
answer << result[i].numer;
answer << "/";
answer << result[i].Den;
}
answer << endl;
i++;
k = 0;
}
answer.close();
}

四、测试运行

随机生成10道题目



改变题目数值后随机生成10道题目



测试在exercisefile.txt文件内填入答案后判断对错


五、PSP表格

PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
Planning 计划 20 20
· Estimate · 估计这个任务需要多少时间 10 10
·Development ·开发 120 100
· Analysis · 需求分析 (包括学习新技术) 60 60
· Design Spec · 生成设计文档 20 20
· Design Review · 设计复审 (和同事审核设计文档) 10 10
· Coding Standard · 代码规范 (为目前的开发制定合适的规范) 5 10
· Design · 具体设计 30 50
· Coding · 具体编码 180 200
· Code Review · 代码复审 20 15
· Test · 测试(自我测试,修改代码,提交修改) 20 20
Reporting 报告 30 30
· Test Report · 测试报告 20 15
· Size Measurement · 计算工作量 10 10
· Postmortem & Process Improvement Plan · 事后总结, 并提出过程改进计划 30 30
合计 585 600

六、项目小结

郭奕材小结:

1、在本次项目中,我们通过结对编程,互相鼓励,相互监督,以更高的效率完成了本次任务,充分的认识到了结对编程的益处,理解它在编程效率与错误检查上的巨大作用。当然也了解到了它的缺点,尤其是在疫情期间沟通不便的客观环境下,沟通的难度也大大增加,但多亏网络会议的共享屏幕功能、微信电话的便捷、双方的耐心沟通和积极讨论等因素,让结对编程的缺点方面也得到了极大的补足。

2、在项目过程中,我学习了中缀表达式转后缀表达式的方法、计算后缀表达式的规则、判断两个题目是否等价的方法等知识,还向搭档学习了如何写更加规范的接口文件,收货颇丰。

3、另外图形化界面目前还在学习,只能做出简单的界面,未能完成项目扩展部分的需求,这是一个小小的遗憾,还需激励自己更快的学习和掌握知识,更好地完成项目需求。

4、最后再次感谢我的搭档陈余同学。

陈余小结:

1、这次项目,是我们第一次一起合作完成的项目,通过结对编程的编程方法,我们十分有效率地完成了项目需求途中出现了不少分歧点,但通过结对编程,我们都能及时地进行讨论和统一观点,使时间没有过多地浪费在不必要的地方

2、原本想让用户在程序里面也能填写答案,但由于想不到比较好的交互方式,所以最后没有实现这个功能,只让用户在txt文件上手动填写答案。

3、最后,和奕材带佬一起做项目真的很舒服!!


七、参考来源

[1] 波兰式、逆波兰式与表达式求值

结对项目 实现自动生成四则运算题目的程序 (C++)的更多相关文章

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

    Github项目地址:https://github.com/huihuigo/expgenerator 合作者:马文辉(3118005015).卢力衔(3118005013) 项目简介 1题目:实现一 ...

  2. 作业二:个人编程项目——编写一个能自动生成小学四则运算题目的程序

    1. 编写一个能自动生成小学四则运算题目的程序.(10分)   基本要求: 除了整数以外,还能支持真分数的四则运算. 对实现的功能进行描述,并且对实现结果要求截图.   本题发一篇随笔,内容包括: 题 ...

  3. Individual Project "写一个能自动生成小学四则运算题目的程序"

    一.题目简介 写一个能自动生成小学四则运算题目的程序. 初步拟定要实现的功能后,估计一下自己需要花多长时间.编程过程中记录自己实际用了多长时间. 然后和同学们比较一下各自程序的功能.实现方法的异同等等 ...

  4. 2018-2019-2 《Java程序设计》结对项目阶段总结《四则运算——整数》(二)

    20175218 2018-2019-2 <Java程序设计>结对项目阶段总结<四则运算--整数> 一.需求分析 实现一个命令行程序,要求: 自动生成小学四则运算题目(加,减, ...

  5. 20194651—自动生成四则运算题第一版报告chris

    1.需求分析: (1)自动生成四则运算算式(+ - *  /),或两则运算(+  -). (2)剔除重复算式. (3)题目数量可定制. (4)相关参数可控制. (5)生成的运算题存储到外部文件中. 2 ...

  6. 新版本ADT创建Android项目无法自动生成R文件解决办法

    本人使用的是ADT是Version 23.0.2,支持Android 6.0之后的系统环境,最高版本23,在创建Android项目的时候,每次创建项目选择“Compile With”低于6.0版本的时 ...

  7. 基于c编写的关于随机生成四则运算的小程序

    基于http://www.cnblogs.com/HAOZHE/p/5276763.html改编写的关于随机生成四则运算的小程序 github源码和工程文件地址:https://github.com/ ...

  8. C# 处理Word自动生成报告 四、程序处理

    C# 处理Word自动生成报告 一.概述 C# 处理Word自动生成报告 二.数据源例子 C# 处理Word自动生成报告 三.设计模板 C# 处理Word自动生成报告 四.程序处理 现在说一下程序处理 ...

  9. C语言:一个能自动生成小学四则运算题目的程序

    完成这个程序,半个小时内完成了,这个程序,可以自动生成小学简易的四则运算,提供菜单让用户选择,然后判断加减乘除,判断答对答错的题目个数,用户同时也可以重新选择继续答题或重新选择或退出程序. 源程序: ...

随机推荐

  1. 图解 JVM 核心知识点(面试版)

    一.基本概念 1.1 OpenJDK 自 1996 年 JDK 1.0 发布以来,Sun 公司在大版本上发行了 JDK 1.1.JDK 1.2.JDK 1.3.JDK 1.4.JDK 5,JDK 6 ...

  2. vue“欺骗”ueditor,实现图片上传

    一.环境介绍 @vue/cli 4.3.1 webpack 4.43.0 ueditor1.4.3.3 jsp版 二.springboot集成ueditor,实现分布式图片上传 参考我的另一篇博客,& ...

  3. Python随机数函数

    Python随机数函数: ''' choice(seq) 从序列的元素中随机选出一个元素 randrange ([start,] stop [,step]) 从指定范围内,在指定步长递增的集合中 获取 ...

  4. pdb 进行调试

    import pdb a = 'aaa' pdb.set_trace( ) b = 'bbb' c = 'ccc' final = a+b+c print(final) import pdb a = ...

  5. hashlib加密算法

    # import hashlib # mima = hashlib.md5()#创建hash对象,md5是信息摘要算法,生成128位密文 # print(mima) # # mima.update(' ...

  6. Numpy数组的函数

    import numpy as np # 将 0~100 10等分 x = np.arange(0,100,10) # array([ 0, 10, 20, 30, 40, 50, 60, 70, 8 ...

  7. 牛客练习赛64 如果我让你查回文你还爱我吗 线段树 树状数组 manacher 计数 区间本质不同回文串个数

    LINK:如果我让你查回文你还爱我吗 了解到了这个模板题. 果然我不会写2333... 考试的时候想到了一个非常辣鸡的 线段树合并+莫队的做法 过不了不再赘述. 当然也想到了manacher不过不太会 ...

  8. bzoj 1515 [POI2006]Lis-The Postman 有向图欧拉回路

    LINK:Lis-The Postman 看完题觉得 虽然容易发现是有向图欧拉回路 但是觉得很难解决这个问题. 先分析一下有向图的欧拉回路:充要条件 图中每个点的入度-出度=0且整张图是一个强连通分量 ...

  9. mysql8.0以上版本修改密码问题记录

    参考链接: https://blog.csdn.net/qq_27820551/article/details/101488430 https://blog.csdn.net/mukouping82/ ...

  10. 【NOIP2015】斗地主 题解(DFS+贪心)

    题目描述 牛牛最近迷上了一种叫斗地主的扑克游戏.斗地主是一种使用黑桃.红心.梅花.方片的AAA到KKK加上大小王的共545454张牌来进行的扑克牌游戏.在斗地主中,牌的大小关 系根据牌的数码表示如下: ...