[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. 1 struct Variable
  2. 2 {
  3. 3 char *val;
  4. 4 int left = 0; //1:变量有左侧有左括号
  5. 5 int right = 0;
  6. 6 int ator = 0; //1:变量为分数
  7. 7 int size = 0;
  8. 8 };

括号生成函数

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

核对答案代码说明

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

分数结构体的声明。

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

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

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

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

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

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


测试运行

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

    可支持生成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. 在Mac上ppt导出pdf

    Step1:打开要操作的ppt,然后Command+P(print),出来打印的界面 Step2:在左下端选择Save as PDF就可以

  2. java:注解(一)

    什么是注解 注解(Annotation),也叫(metadata)元数据.一种代码级别的说明.它是JDK1.5及以后版本引入的一个特性,与类.接口.枚举是在同一个层次.它可以声明在包.类.字段.方法. ...

  3. 我如何添加一个空目录到Git仓库?

    新建了一个仓库,只是创建一些目录结构,还不里面放什么,要放的内容还没有,还不存在,应该怎么办呢? Git 是不跟踪空目录的,所以需要跟踪那么就需要添加文件! 也就是说 Git 中不存在真正意义上的空目 ...

  4. 30:根据排序标识flag给数组排序

    题目描述:输入整型数组和排序标识,对其元素按照升序或降序进行排序 接口说明 原型: void sortIntegerArray(Integer[] pIntegerArray, int iSortFl ...

  5. Mac中遇到的Eclipse连接不上mySql的问题

    1.首先我们在eclipse中连接数据库的过程中,遇到的问题就是如上图.开始百度Communications link failure 这几个关键字.得到的结果基本上就是基本配置参数wait_time ...

  6. 可执行Jar包调用动态链接库(DLL/SO)

    踩过了很多的坑,查了很多资料,在此记录一下,以SpringBoot项目为基础. Maven加入JNA依赖 <!-- JNA start --> <dependency> < ...

  7. servletResponse 文件下载

    package response; import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.IOEx ...

  8. 开启Java远程调试

    在JDK启动时,加入 -Xrunjdwp:transport=dt_socket,address=9900,server=y,suspend=n -Dcom.sun.management.jmxrem ...

  9. Linux下Kafka单机安装配置方法

    Kafka是一个分布式的.可分区的.可复制的消息系统.它提供了普通消息系统的功能,但具有自己独特的设计.这个独特的设计是什么样的呢? 首先让我们看几个基本的消息系统术语: •Kafka将消息以topi ...

  10. Jquery和JS的区别

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...