[Github项目地址]

完成功能:

1. 使用 -n 参数控制生成题目的个数

2. 使用 -r 参数控制题目中数值(自然数、真分数和真分数分母)的范围

3. 生成的题目中计算过程不能产生负数,也就是说算术表达式中如果存在形如e1 − e2的子表达式,那么e1 ≥ e2

4. 生成的题目中如果存在形如e1 ÷ e2的子表达式,那么其结果应是真分数。

5. 每道题目中出现的运算符个数不超过3个。

其中真分数在输入输出时采用如下格式,真分数五分之三表示为3/5,真分数二又八分之三表示为2’3/8。

6. 在生成题目的同时,计算出所有题目的答案,并存入执行程序的当前目录下的Answers.txt文件

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

7. 程序应能支持一万道题目的生成。

8. 程序支持对给定的题目文件和答案文件,判定答案中的对错并进行数量统计,统计结果输出到文件Grade.txt

未完成功能:

1. 程序一次运行生成的题目不能重复

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

程序设计

需求分析

将程序分为表达式生成答案核对两部分

  • 表达式生成所要求的功能有:
    1.随机生成符合正确数学规则的表达式
    2.可以通过参数控制题目中数值的大小
    3.查重。即任何两道题目不能通过有限次交换+和x左右的算术表达式变换为同一道题目
    4.将生成的文件以固定的格式写入txt文件中

  • 答案核对所要求的功能有:
    1.计算txt文件中的数学表达式的答案
    2.将答案文件写入Answers.txt文件中
    3.对给定的题目文件和答案文件,判定答案中的对错,并进行数量统计

解题思路

首先是表达式生成,其核心要满足随机性,特别是括号的生成,一开始我们讨论出两套方案:

  • 一是利用二叉树的中序遍历,其结构如下:

    方法是每次访问父结点时,自动带上括号,这样最终的表达式生成公式为:
    $$
    ((A op1 B)op2(C op3 D))
    $$
    注释:ABCD是变量,op1op2op3是运算符
    但是这个方案不一定是随机的,无法生成一对括号中有三个变量的运算,如 \((3+4+5)*6\)

  • 第二种方案就是正面莽,基于左括号(只能在运算符的后面一位,右括号)只能在变量的后面一位这一特点,分别写左括号生成函数右括号生成函数。每次生成数值或运算符后进行一次判断。

在计算部分,重点在于计算时对分数的处理和各符号优先级的判断。

  • 先定义好表达式结构体和适合计算的分数结构体,分母为1代表该数是整数并且定义好存放分数和运算符的栈,设置好栈的相关操作,用于读题时的优先级判断。

  • 写入一个判断符号优先级的函数,并以字符'>', '<'输出结果。

  • 写好分数结构体的加减乘除算法并整合到一个函数中。

  • 通过一个函数得出一个表达式的答案。

  • 通过一个文件打开函数读入题目并将答案分别存入各个表达式结构体数组的答案元素中,存入Answers.txt。

在核对部分,将<exercisefile>.txt内的答案逐个读入,转为分数形式,再和通过问题文件生成的表达式数组里的答案元素逐个比较,当分子于分母都相等时,判断对,正确数加一,题号存入数组,否则错题数加一,题号存入数组。再分别输出到Grades.txt。


代码说明部分

将变量放入结构体中

1 struct Variable
2 {
3 char *val;
4 int left = 0; //1:变量有左侧有左括号
5 int right = 0;
6 int ator = 0; //1:变量为分数
7 int size = 0;
8 };

括号生成函数

 1 BOOL BCL(char *calproblem, int x, int num)                  //nur after op
2 {
3 extern int LB, RB;
4 switch (rand() % 4)
5 {
6 case 0:
7 {
8 if (LB<2&&(num - x) >= 2)
9 {
10 strcat_s(calproblem, strlen(calproblem) + sizeof(char) + 1, "(");
11 LB++;
12 m_VarStruct[x].left = 1;
13 return 1;
14 }
15
16 }
17 break;
18 case 2:
19 {
20 if (LB == 0 && (num - x >= 3))
21 {
22 strcat_s(calproblem, strlen(calproblem) + 2 * sizeof(char) + 1, "((");
23 LB = 2;
24 m_VarStruct[x].left = 2;
25 return 1;
26 }
27 }
28 break;
29 default:
30 break;
31 }
32 return 0;
33 }
34 BOOL BCR(char *calproblem, int x, int num) //nur after Var
35 {
36 extern int LB, RB;
37 if (RB == 2 || LB==0) return 0;
38 if (LB <= 2)
39 {
40 switch (rand() % 2)
41 {
42 case 1:
43 {
44 if (!m_VarStruct[x].left)
45 {
46 strcat_s(calproblem, strlen(calproblem) + strlen("(") + sizeof("\0"), ")");
47 RB++;
48 m_VarStruct[x].right = 1;
49 return 1;
50 }
51 }
52 break;
53 case 0:
54 {
55 if (!m_VarStruct[x].left&&LB == 2 && RB == 0)
56 {
57 strcat_s(calproblem, strlen(calproblem) + 2 * sizeof(char) + 1, "))");
58 RB = 2;
59 m_VarStruct[x].right = 2;
60 return 1;
61 }
62 }
63 break;
64 default:
65 break;
66 }
67 }
68 return 0;
69 }

核对答案代码说明

1 typedef struct {             //分数结构体,分母默认为1,即整数
2 int numerator = 0; //分子
3 int denominator = 1; //分母
4 }Element; //计算项结构体

分数结构体的声明。

 1 int GetGreatestCommonFactor(int a, int b){  //返回最大公因数
2 int temp = 0;
3 while(a!=0){
4 temp = b % a;
5 b = a;
6 a = temp;
7 }
8 return b;
9 }
10
11 Element Add(Element e1,Element e2){ //加法
12 Element e3;
13 int n = 0;
14 int d = 0;
15 n = e1.numerator * e2.denominator + e2.numerator * e1.denominator;
16 d = e2.denominator * e1.denominator;
17 int C = GetGreatestCommonFactor(n, d); //求出分子和分母的最大公因数
18 e3.numerator = n / C;
19 e3.denominator = d / C;
20 return e3;
21 }
22
23 Element minus(Element e1,Element e2){ //减法,前一个是被减数,后一个是减数
24 Element e3;
25 int n = 0;
26 int d = 0;
27 n = e1.numerator * e2.denominator - e2.numerator * e1.denominator;
28 d = e2.denominator * e1.denominator;
29 int C = GetGreatestCommonFactor(n, d);
30 e3.numerator = n / C;
31 e3.denominator = d / C;
32 return e3;
33 }
34
35 Element Multiply(Element e1,Element e2){
36 Element e3;
37 int n = 0;
38 int d = 0;
39 n = e1.numerator * e2.numerator;
40 d = e2.denominator * e1.denominator;
41 int C = GetGreatestCommonFactor(n, d); //求出分子和分母的最大公因数
42 e3.numerator = n / C;
43 e3.denominator = d / C;
44 return e3;
45 }
46
47 Element Divide(Element e1,Element e2){ //除法,前一个是被除数,后一个是除数
48 Element e3;
49 int n = 0;
50 int d = 0;
51 n = e1.numerator * e2.denominator;
52 d = e1.denominator * e2.numerator;
53 int C = GetGreatestCommonFactor(n, d);
54 e3.numerator = n / C;
55 e3.denominator = d / C;
56 return e3;
57 }

分数的加减乘除并化为既约分数的过程。

 1 char CompareOp(char op1, char op2) {
2 char c;
3 switch (op2) {
4 case '+':
5 case '-': {
6 if (op1 == '(' || op1 == '=') c = '<';
7 else c = '>';
8 break;
9 }
10 case '*':
11 case -62: {
12 if (op1 == '*' || op1 == -62 || op1 == ')') c = '>';
13 else c = '<';
14 break;
15 }
16 case '(': {
17 if (op1 == ')') {
18 printf("错误输入\n");
19 return -1;
20 }
21 else c = '<';
22 break;
23 }
24 case ')': {
25 switch (op1) {
26 case '(': {
27 c = '=';
28 break;
29 }
30 case '=': {
31 printf("错误输入\n");
32 return -1;
33 }
34 default:c = '>';
35 }
36 break;
37 }
38 case '=': {
39 switch (op1) {
40 case '=': {
41 c = '=';
42 break;
43 }
44 case '(': {
45 printf("错误输入\n");
46 return -1;
47 }
48 default: c = '>';
49 }
50 }
51 }
52 return c;
53 }

这里是运算符优先级判断函数,输入两个运算符,对运算符进行优先级判断,当出现一对完整的括号时,输出等于(从而立即计算括号内的式子)。

 1 Element AnAnswer(char s[40]){     //计算得出答案项
2 NumberStack numberstack;
3 OpStack opstack;
4 Element num1,num2,result,num;
5 char c,sign;
6 char *str = NULL;
7 int count = 0;
8
9 InitNumberStack(&numberstack);
10 InitOpStack(&opstack);
11
12 PushOpStack(&opstack, '=');
13 int j = 0;
14 while(s[j] != '\t') j++; //越过题号,来到题目开始位置
15 int i = j;
16 j = 0;
17 c = s[i];
18 while((c != '=')||opstack.op[opstack.top] != '='){
19 if(JudgeOp(c) == 0){
20 str = (char*)malloc(sizeof(char)*12);
21 do{
22 *str = c;
23 str++;
24 count++;
25 i++;
26 c = s[i];
27 }while(JudgeOp(c) == 0); //提取数字字符串
28 *str = '\0';
29 str = str - count;
30 num = GetNumberFromStr(str);//数字字符串化为分数结构体
31 PushNumberStack(&numberstack, num);
32 str = NULL;
33 count = 0;
34 }
35 else{
36 switch(CompareOp(opstack.op[opstack.top], c)){
37 case '<':{
38 PushOpStack(&opstack, c);
39 i++;
40 c = s[i];
41 break;
42 }
43 case '=':{
44 sign = PopOpstack(&opstack);
45 i++;
46 c = s[i];
47 break;
48 }
49 case '>':{
50 sign = PopOpstack(&opstack);
51 num2 = PopNumberStack(&numberstack);
52 num1 = PopNumberStack(&numberstack);
53 result = CaculateOneOp(num1, sign, num2);
54 PushNumberStack(&numberstack, result);
55 break;
56 }
57
58 }
59 }
60 }
61 result = numberstack.e[numberstack.top];
62 return result;
63 }

在优先级判断中,如果是“<”,意味着之后读到的运算符优先级更高要先算,所以放入栈,获取下一个运算符,如果是“=”,意味着括号配对出现,则将栈里的左括号弹出,如果是">",意味着之后的运算符优先级较低,先将之前的运算级较高的运算符计算完后答案入栈,再进行接下来的判断。


测试运行

***********************
可用参数如下:
-r <题目的数字最大值>
-n <题目的数量>
-e <题目文件位置>
-a <答题卡文件位置>
-h "帮助"
***********************
  • 表达式生成部分

    可支持生成100k道题目
    生成10k道题目的时间为18ms的CPU时间

  • 答案生成部分

  

  生成10k道题目的时间为63ms的CPU时间

  • 核对答案部分

  答题卡(测试文件)包括五道正确,两道错误,其余为空  

  

   成绩

  

  对比10k道题目的时间为55ms的CPU时间

效能分析

程序中最占时间的函数是SaveAnswersAnAnswerOoenFielAndGiveTheAnswerCompareAndGiveTheGrade四个函数,都是和答案生成有关,其中三个函数都调用了AnAnswer函数,这个函数的定义是:

Element AnAnswer(char s[40]); //计算得出答案项,并将答案存入结构体中

AnAnswer函数的调用情况如下:

可以看到AnAnswer函数中最耗时间的是malloc内存分配,第二耗时的是GetNumberFromStr()函数,其定义如下

Element GetNumberFromStr(char s[]); //从含单个数的字符数组中得出相应的值,将int转化成字符数组存储

其中最耗时的是int转化为字符数组的库函数itoa


项目小结

在本次开发中,我遇到的最困难的问题就是内存分配问题,特别是数组越界的判断,真的是 不怕恋人出轨,就怕数组出界
存放表达式的数组原本我想设计成响应式的,即可以根据不同数值的变量分配不同大小的内存,以减少内存的使用,但是一直触发内存访问错误的断点。大改了两三次,重写了一次,小修补十几次。
已探明的问题主要在以下几个方面:
1.数组大小估计不足。主要是因为对数组的概念不明确

char str[20]="0123456789";

int a=strlen(str); //a=10; >>>> strlen 计算字符串的长度,以结束符 0x00 为字符串结束。
int b=sizeof(str); //而b=20; >>>> sizeof 计算的则是分配的数组 str[20] 所占的内存空间的大小,不受里面存储的内容改变。

上面是对静态数组处理的结果,如果是对指针,结果就不一样了
char* ss = "0123456789";

sizeof(ss) 结果 4 ===》ss是指向字符串常量的字符指针,sizeof 获得的是一个指针的之所占的空间,应该是长整型的,所以是4
sizeof(*ss) 结果1 ===》*ss是第一个字符 其实就是获得了字符串的第一位'0' 所占的内存空间,是char类型的,占了 1 位
strlen(ss)= 10 >>>> 如果要获得这个字符串的长度,则一定要使用 strlen

char [0] [1] [2] [3] [4] [5] [6] [7] [8] [9]
  A B C D E F G H \0 \0

如果没有结尾的\0,数组就会出界。callocmalloc两个函数直接输入所需元素的个数,编译器会自动分配一个空间存放\0接在后面。
2.字符数组结尾没有加上\0,这个错误主要是strcpy_sstrcat_s两个函数中关于数组大小参数的使用。

【C】四则运算生成和核对器----by郁卓、谢明浩的更多相关文章

  1. 软件工程启程篇章:C#和四则运算生成与运算

    0x01 :序言 I leave uncultivated today, was precisely yestoday perishes tomorrow which the person of th ...

  2. 【第二次个人作业】结对作业Core第一组:四则运算生成PB16061082+PB16120517

    [整体概况] 1.描述最终的代码的实现思路以及关键代码. 2.结对作业两个人配合的过程和两个人分工. 3.API接口文档和两个组的对接. 4.效能分析,优化分析和心得体会. [代码实现] 一. 实现功 ...

  3. 四则运算生成命令行程序 (Python)

    Github项目地址:Github Pages 结对项目成员:张鹏 3118004985 郑靓 3118004988 一.项目需求分析 二.功能实现 三.代码实现or功能说明 ★ GUI功能扩展说明 ...

  4. 四则运算生成与校检 Python实现

    GitHub地址 https://github.com/little-petrol/Arithmetic.git 合作者: 郭旭 和 卢明凯 设计实现过程 代码的组织主要分为两个部分: 算法与结构体的 ...

  5. 软件工程启程篇章:结对编程和进阶四则运算(197 & 199)

    0x01 :序言:无关的事 I wrote a sign called "Dead End" in front of myself, but love crossed it wit ...

  6. GIS案例学习笔记-三维生成和可视化表达

    GIS案例学习笔记-三维生成和可视化表达 联系方式:谢老师,135-4855-4328,xiexiaokui#qq.com 目的:针对栅格或者矢量数值型数据,进行三维可视化表达 操作时间:15分钟 案 ...

  7. ArcGIS自定义脚本-通过txt/excel/dbf/table生成多边形要素类

    ArcGIS自定义脚本-通过txt/excel/dbf/table生成多边形要素类 联系方式:谢老师,135-4855-4328,xiexiaokui#qq.com 目的:读取文本文件,常见多边形要素 ...

  8. [2017BUAA软工助教]博客格式的详细说明

    一.为什么要强调博客格式 可以对比粗读一下这几篇博客然后自己感受一下博客格式对博客阅读体验的影响: MarkDown流:    [schaepher]2017春季 JMU 1414软工助教 链接汇总 ...

  9. 105&250-高级软件工程2017第3次作业

    小组成员 2017282110250 王婷婷 2017202110105 张芷祎 github地址 https://github.com/setezzy/Calculator_GUI PSP PSP2 ...

随机推荐

  1. Linux在中国正在走向没落

    在中国,Linux正在走向没落,一片萧条景象. 在这样的大背景下.居然有人愿意接手中科红旗,令人佩服! 在中国,没有一个关于国际Linux的官方刊物(或站点)反映国际Linux运动的真实声音.Linu ...

  2. Centos6.0使用第三方YUM源(EPEL,RPMForge,RPMFusion)

    yum是centos下很方便的rpm包管理工具,配置第三方软件库使你的软件库更加丰富.以下简单的讲下配置的步骤. 首先,需要安装yum-priorities插件: yum install yum-pr ...

  3. 吐血整理:PyTorch项目代码与资源列表 | 资源下载

    http://www.sohu.com/a/164171974_741733   本文收集了大量基于 PyTorch 实现的代码链接,其中有适用于深度学习新手的“入门指导系列”,也有适用于老司机的论文 ...

  4. 在diy的文件系统上创建文件的流程

    [0]README 0.1) source code are from orange's implemention of a os , and for complete code , please v ...

  5. A charge WIFI point base on airbase-ng+dhcp+lamp+wiwiz

    Make wifi as a hot point Make a script echo $0 $1 case $1 in "start") sleep 1 ifconfig wla ...

  6. C# Array类的浅复制Clone()与Copy()的差别

    1 Array.Clone方法 命名空间:System 程序集:mscorlib 语法: public Object Clone() Array的浅表副本仅复制Array的元素,不管他们是引用类型还是 ...

  7. Yii的权限管理rbac

    1.首先我们要在配置文件的组件(component)里面配置一下 Rbac 在对应项目下的config/main.php或者config/main-local.php下添加 'authManager' ...

  8. CentOS6.5安装MySQL5.6 过程记录

    刚开始,还不太懂,直接上了MySQL5.7版本的二进制安装,结果遇到了各种问题,从5.6到5.7还是做了很大改变的,比如mysql_install_db的文件位置变更到了/bin文件下等等,觉得现在用 ...

  9. 【题解】[P4178 Tree]

    [题解]P4178 Tree 一道点分治模板好题 不知道是不是我见到的题目太少了,为什么这种题目都是暴力开值域的桶QAQ?? 问点对,考虑点分治吧.直接用值域树状数组开下来,统计的时候直接往树状数组里 ...

  10. i=i+2 与i+=2

    i=i+2 比 i+=2多了一次对变量 i 的运算.后者效率高