2016012034+小学四则运算练习软件项目报告

代码仓库地址:https://git.coding.net/Siamese_miao/fourArithmetic.git

测试效果见result.txt文件


 

一、 需求分析与功能设计

1.  使用JAVA编程语言。

2.  接收一个输入参数n,随机产生n道四则运算练习题,符号用+-*÷来表示。

3.  每个数字在 0 和 100 之间。

4.  运算符在3个到5个之间并且每道练习题至少要包含2种运算符。

5.  运算过程中不得出现负数与非整数。

6.  将学号和生成的练习题及对应答案输出到文件“result.txt”中,不输出额外信息。

附加需求

1.  产生带括号的四则运算并求解,算式中存在的括号必须大于2个,且不得超过运算符的个数。

2.  产生真分数的加减法运算,运算过程中都为最简真分数。

基本功能

    能够根据用户输入的参数n随机产生n道符合要求的四则混合运算练习题,自动算出答案,并将式子与答案以文档的形式呈现。

扩展功能    

    支持有括号的运算、支持最简真分数的加减运算


 

二、 设计实现

    首先,我从简单四则混合运算开始,接着在这的基础上完成了分数运算,最后再思考带括号的运算。我先按这个顺序分别创建了项目,单独编写,最后才分类规整到一个项目中,如图我分为5个类。

  • Main类:主类,负责接收命令行的参数并启动程序。
  • fileCreate类:创建文件类,负责每次出题类型并产生result.text文件,将学号与练习题写入文件。
  • formula类:式子类,负责根据调用产生同种类型的式子,含有arithmetic(简单四则运算)、bracket(带括号的四则运算)、score(真分数加减运算)三种函数。
  • calculate类:计算类,负责各种计算,含有结果运算、有条件产生减数、有条件产生除数、有条件产生分子、有条件产生分母、判断数的大小、求取最大公因数、求取最小公倍数、分数相加、分数相减等10个方法。

   各类与各函数之间的关系如下图。


 

三、 算法详解

    我的算法大多数比较简单,多采用递归的方式。

  • 简单四则运算

    由于符号由+-*÷表示,而计算机计算使用+-*/,所以我先定义了两个符号数组与定义两个字符串,一个作为显示式子用,一个用于计算,数组下标一一对应。再由范围为3~5的随机数确定式子的符号数,再由两个整型数组存储算数与符号对应的下标值,值皆由随机数产生,算数范围为0~100,下标值范围为0~3。式子使用for循环,每次将一个算数与一个符号加入字符串中,执行完循环后,最后再加上最后一个算数。计算后,连同式子一并输出。在计算时,我使用java调用js中的eval(String)函数求解。因为式子必须包含两种运算符以上,在输出前需要判断是否所有符号相同,不相同则不输出并重新运行程序。

 

 1 // 计算结果
 2     public static Object result(String temp) {
 3         ScriptEngineManager sem = new ScriptEngineManager();
 4         ScriptEngine se = sem.getEngineByName("js");
 5         Object last = 0;
 6         try {
 7             last = se.eval(temp);
 8         } catch (ScriptException e) {
 9             e.printStackTrace();
10         }
11         return last;
12     }
计算结果

   

    考虑到运算过程中不能出现小数与负数,所以在除号与减号部分需要做处理,当减数大于被减数时,重新生成减数直至不大于被减数,当除数无法整除被除数或为0时,重新生成除数。后来在测试时发现两两数字符合规则,然而整条式子却仍会出现负数与小数。经过思考,一般出现于连减、连除、减号后乘除因优先级不同的式子中,所以我设了个判断,减号后一位只能为加号,除号后一位只能为加减号,由此解决了运算过程中出现负数或小数的问题。

 

 1 public static String arithmetic() {
 2         int m, j;
 3         char[] p = new char[] { '*', '+', '÷', '-' };
 4         char[] q = new char[] { '*', '+', '/', '-' };
 5         String temp1 = "";
 6         String temp2 = "";
 7         m = (int) (Math.random() * 3 + 3); // 符号数
 8         int[] num = new int[m + 1]; // 数字
 9         int[] key = new int[m]; // 符号所在的下标
10         for (j = 0; j <= m; j++) {
11             num[j] = (int) (Math.random() * 101);
12         }
13         for (j = 0; j < m; j++) {
14             if (j > 0 && key[j - 1] == 3) { // 减号后仅允许加号,防止负数出现
15                 key[j] = 1;
16             } else if (j > 0 && key[j - 1] == 2) {
17                 key[j] = (int) (Math.random() * 2); // 除号后仅允许乘号与加号,防止负数
18             } else {
19                 key[j] = (int) (Math.random() * 4); // 随机符号
20             }
21             temp1 += String.valueOf(num[j]) + String.valueOf(p[key[j]]);
22             temp2 += String.valueOf(num[j]) + String.valueOf(q[key[j]]);
23             if (key[j] == 3) {
24                 num[j + 1] = calculate.decide1(num[j], num[j + 1]); // 选定小于被减数的减数
25             } else if (key[j] == 2) {
26                 num[j + 1] = calculate.decide2(num[j], num[j + 1]); // 确保能够整除
27             }
28         }
29         j = 0;
30         while (j < (m - 1) && key[j] == key[j + 1])
31             j++; // 与第一个符号相同数
32         if (j == (m - 1))
33             return arithmetic(); // 若所有符号相同,该式子不算,保证有两种运算符
34         else {
35             temp1 += String.valueOf(num[m]);
36             temp2 += String.valueOf(num[m]);
37             return temp1 + "=" + calculate.result(temp2);
38         }
39     }
简单四则运算

 

  • 分数的加减

    在基本混合运算的基础上修改出分数加减就显得简单许多。最主要的就是分数的通分约分,解决了这个问题,程序就基本完成了。最大公因数我之前使用了for循环挨个计算,今天(3月28日)我想起了辗转相除法,修改之后,运算速度明显提高。

   

 1     public static int gcd(int x, int x2) {
 2         int s = 1;
 3         x = Math.abs(x);
 4         x2 = Math.abs(x2);
 5         while (x2 != 0) {
 6             s = x % x2;
 7             x = x2;
 8             x2 = s;
 9         }
10         return x;
11     }
求最大公因数(辗转相除法)

 

  • 含括号的运算

    这部分由于优先级的改变以及时间关系,经过改善,目前已经基本确保运算结果不含小数与负数。

 

1 // 判断是否为小数
2     public static boolean judgeIsDecimal(String num) {
3         boolean isdecimal = false;
4         if (num.contains(".")) {
5             isdecimal = true;
6         }
7         return isdecimal;
8     }
判断小数

 

    我思考了很久如何随机生成括号,最后受到一篇博客(当时忘记保存网址,现在已经找不到了,抱歉)使用概率的启发,我以一定概率的方式生成左括号,记录生成左括号的个数以及未匹配的左括号的个数,以一定概率生成右括号,最后补齐。

 

 1 if (((brack * 2) <= (n - 1)) && (((int) (Math.random() * 2)) == 0)) // 以一定概率生成左括号,概率为1/2
 2             {
 3                 temp1 += "(";
 4                 temp2 += "(";
 5                 brack++;
 6                 brack_left++;
 7                 temp1 += num[++i]; // 生成左括号后必须生成一个数字和运算符,不然可能出现(15)这样的错误
 8                 temp2 += num[i];
 9                 op = (int) (Math.random() * 4);
10                 temp1 += Op[op];
11                 temp2 += p[op];
12                 if (op == 3)
13                     div = 1;
14                 else if (op == 1)
15                     div = 2;
16             }
概率生成左括号

 


 

四、 测试运行

进入src文件下,输入javac -encoding utf-8 Main.java 编译出相应的class文件,再输入java Main 20进行测试,我们可以先测试java Main abc或java Main 1500或java Main 0,在这里我使用的jdk版本为jdk1.8.0_25。

测试结果如下图。

除此之外,我还学习了使用myeclipse做单元测试,测试结果如图所示,我从测试结果发现,当文件存在时,删除重建会耗时约2秒(以5道算式为例)。

 


 

五、代码片段

    分数的加减计算因为没有优先级的关系,可以一边补充式子一边计算从而修改算数,无需限定符号。这也是我最满意的代码。

 

 1 // 真分数分式
 2     public static String score() {
 3         char[] p = new char[] { '+', '-' };
 4         int j;
 5         String temp1 = "";
 6         int m = (int) (Math.random() * 3 + 3);
 7         int[] key = new int[m]; // 运算符
 8         int[] x = new int[m + 1]; // 分子
 9         int[] y = new int[m + 1]; // 分母
10         int[] sum = new int[2];// 中途运算结果
11         for (j = 0; j <= m; j++) {
12             x[j] = (int) (Math.random() * 20 + 1);
13             y[j] = calculate.decide3(x[j]);
14         }
15         sum[0] = x[0];
16         sum[1] = y[0];
17         for (j = 0; j < m; j++) {
18             key[j] = (int) (Math.random() * 2);
19             if (key[j] == 0) { // 结果小于1
20                 int[] num = new int[2];
21                 num = calculate.fracAdd(sum[0], sum[1], x[j + 1], y[j + 1]);
22                 if (num[0] >= num[1]) {
23                     key[j] = 1;
24                 } else {
25                     sum = num;
26                 }
27             }
28             if (key[j] == 1) { // 结果不为负数
29                 int[] num = new int[2];
30                 num = calculate.fracSub(sum[0], sum[1], x[j + 1], y[j + 1]);
31                 if (num[0] < 0) {
32                     x[j + 1] = calculate.decide4(sum[0], sum[1]);
33                     y[j + 1] = sum[1];
34                     num = calculate.fracSub(sum[0], sum[1], x[j + 1], y[j + 1]);
35                 }
36                 sum = num;
37             }
38             temp1 += String.valueOf(x[j]) + "/" + String.valueOf(y[j]) + String.valueOf(p[key[j]]);
39         }
40         j = 0;
41         while (j < (m - 1) && key[j] == key[j + 1])
42             j++; // 与第一个符号相同数
43         if (j == (m - 1))
44             return score(); // 若所有符号相同,该式子不算,保证有两种运算符
45         else {
46             temp1 += String.valueOf(x[m]) + "/" + String.valueOf(y[m]);
47             return temp1 + "=" + sum[0] + "/" + sum[1];
48         }
49     }
真分数运算

 


 

六、 总结

    一开始由于我不清楚我能否完成附加功能,所以我用一个Main类的主函数完整的写一个简单的四则运算。但是调试时花费了许多时间,并且十分不灵活,所以我把函数中重复的计算部分抽离出来,写成静态方法,再进行测试,调试起来方便许多,想要修改哪一个部分的代码只需要在相应的函数中修改即可。同理,分数运算与括号运算我也这么做,由此我创建了三个项目。当三个项目都差不多完成时,我意识到其中有许多重复之处,所以我将它们分类并合在一个项目中。

    除了主类包含了一个主函数外,我把我的程序分成3个类,各自分工,每个类中我尽量把各个功能细分成各种小方法,尤其在calculate类中每个方法不超过10行。具体可看第三点的关系图。方法间的逐级调用给调试和测试都带来了很多便利,尤其在测试优化方面,同时也增加了代码的可移植性和可读性。


 

七、 PSP

PSP2.1

任务内容

计划共完成需要的时间(min)

实际完成需要的时间(min)

Planning

计划

30

20

·        Estimate

·   估计这个任务需要多少时间,并规划大致工作步骤

30

20

Development

开发

1515

2470

·        Analysis

·         需求分析 (包括学习新技术)

180

150

·        Design Spec

·         生成设计文档

50

30

·        Design Review

·         设计复审 (和同事审核设计文档)

10

15

·        Coding Standard

·         代码规范 (为目前的开发制定合适的规范)

5

5

·        Design

·         具体设计

10

15

·        Coding

·         具体编码

1200

1800

·        Code Review

·         代码复审

30

15

·        Test

·         测试(自我测试,修改代码,提交修改)

30

440

Reporting

报告

75

170

·         Test Report

·         测试报告

10

15

·         Size Measurement

·         计算工作量

5

5

·         Postmortem & Process Improvement Plan

·         事后总结, 并提出过程改进计划

60

150

    这次的代码我思考了很久,在敲代码与测试方面远远的超过了计划的时间,源于我低估了代码的复杂度。虽然有思路,但把思路实现却花费了特别多的时间,思虑的不充分总是让程序报错,再加上我对java掌握的不够,所以我在编程花费了许多许多时间,有很多时候就是不知如何实现需求。再者,打这篇博客报告也花费了很多时间,但也再次整体的梳理了我的思路。

    在这次打代码的过程中,我学到了四个方法。

    一个是使用java调用js的eval()函数,这个方法可以输入字符串型的算式然后直接算出答案。

    另外一个是在用命令运行符使用Java Main 20作为命令时,应使用

 

1 n=Integer.parseInt(args[0]);

 

来获取输入的参数,而我在myeclipse使用的一直是

 

1 Scanner in = new Scanner(System.in);
2 n=in.nextInt();

 

语句执行。

   还有一个是以概率的方式随机生成括号。

   最后是使用Debug修改错误代码,使用单元测试优化程序提高性能。


 

   这次的学习让我意识到自己的不足,我以后会继续努力。

posted @ 2018-03-24 23:43  尼姑说她喜欢猫  阅读(489)  评论(7编辑  收藏  举报