项目源码地址:https://git.coding.net/Donsparks/project1.git
测试效果见src下生成的result.txt文件
一、需求分析
- 程序可从命令行接收一个输入参数n,然后随机产生n道加减乘除练习题。
- 每个数字在0和100之间,运算符3个到5个之间。
- 每个练习题至少要包含2种运算符。
- 所出的练习题在运算过程中不得出现负数与非整数。
- 将学号与生成的n道练习题及其对应的正确答案输出到文件“result.txt”中。
- 支持有括号的运算式,包括出题与求解正确答案。注意,算式中存在的括号必须大于2个,且不得超过运算符的个数。(附加)
- 支持真分数的出题与运算(只需要涵盖加减法即可),且支持分数的自动化简。(附加)
二、功能设计
- 基本功能:实现四则运算的功能,并能够将题目输出到文本文件中。
- 扩展功能:能够随机产生带有括号的运算式。可以生成可以真分数的运算表达式,且分数可以自动化简。
三、设计实现
考虑到程序的可扩展性,我将程序分为两个大类。
-
Main类:主要是用来控制命令行的输入,以及对输入格式的规定和限制。
-
File类:主要的功能实现部分。其中的getFile()函数用来生成运算式并生成得到txt文件。
函数间逻辑关系:Main类中通过new一个新的对象,然后来调用File类中的getFile()方法,从而实现运算式的生成,并得到文本文件。
四、算法详解
我采用的算法,是我个人比较擅长的枚举法。通过java中的if语句和switch语句,从而得到随机生成的四则运算。代码如下:
int a = 0; //运算式的第一个随机数 int b = 0; //运算式的第二个随机数 int c = 0; //运算式的第三个随机数 int d = 0; //运算式的第四个随机数 int e = 0; //运算式的第五个随机数 int num = 0; //保存运算式结果 String q = ""; //保存运算式 Random random = new Random(); int i = 0; while (i < x) { a = (int) (Math.random() * 100 + 1); b = (int) (Math.random() * 100 + 1); c = (int) (Math.random() * 100 + 1); d = (int) (Math.random() * 100 + 1); e = (int) (Math.random() * 100 + 1); int z = random.nextInt(5) % (5 - 3 + 1) + 3;//产生一个3-5之间的随机数,用来设定运算式的数值个数 if (z == 3) { //3个数,则产生两个运算符 int A = random.nextInt(4) % (4 - 1 + 1) + 1; switch (A) { case 1: num = a + b; q = a + "+" + b; break; case 2: num = a - b; q = a + "-" + b; break; case 3: num = a * b; q = a + "*" + b; break; default: num = a / b; q = a + "/" + b; break; } int B = random.nextInt(4) % (4 - 1 + 1) + 1; switch (B) { case 1: num = num + c; q = q + "+" + c; break; case 2: num = num - c; q = q + "-" + c; break; case 3: num = num * c; q = q + "*" + c; break; default: num = num / c; q = q + "/" + c; break; } ...... }
五、测试运行
1、在命令行中的运行结果展示:
2、生成的txt文件展示:
六、部分代码展示
1、片段一:
int i = 0; while (i < x) { ...... String hush = String.valueOf(jse.eval(q)); Double result = Double.parseDouble(hush); if (result < 0 || result % 1 != 0) { continue; } //保证结果不为负数或者非整数 fop.write((q + "=" + jse.eval(q)).getBytes()); //写入表达式 fop.write(" ".getBytes()); //换行 System.out.println(q + "=" + jse.eval(q)); q = ""; result = (double) 0; i++; //重新置为空,重新开始 }
此部分代码通过一个while语句,实现了一次循环输出,并且还能够判断结果的数值,保证输出的结果全部是负数或整数。
此外,这样一来,无论我们要生成多少道题,都可以马上算出来,无论是否超出此次项目的规定的0到1000道。这也是我个人觉得非常不错的一点。但是同样也存在一些不足,比如代码运行效率较低,主要原因还是在于这里是先得到运算结果然后再判断是否符合要求,然后再决定是否保留这一运算式。这样一来,就会程序就需要跑一些没必要的运算式。从而浪费了很多空间和时间,代码效率较低。
当然,针对这一问题,我也有一些改进的想法,比如可以直接就选择一些能够整除,或者减数小于被减数的数字。从而减少一些没必要的运算。
2、片段二:
int a = 0; //运算式的第一个随机数 int b = 0; //运算式的第二个随机数 int c = 0; //运算式的第三个随机数 int d = 0; //运算式的第四个随机数 int e = 0; //运算式的第五个随机数 int num = 0; //保存运算式结果 String q = ""; //保存运算式 int i = 0; //x是通过命令行输入的数字,即要生成的题目数 while (i < x) { a = (int) (Math.random() * 100 + 1); b = (int) (Math.random() * 100 + 1); c = (int) (Math.random() * 100 + 1); d = (int) (Math.random() * 100 + 1); e = (int) (Math.random() * 100 + 1); int z = random.nextInt(5) % (5 - 3 + 1) + 3;//产生一个3-5之间的随机数,用来设定运算式的数值个数 //3个运算数的情况,则产生两个运算符 if (z == 3) { int A = random.nextInt(4) % (4 - 1 + 1) + 1; switch (A) { case 1: num = a + b; q = a + "+" + b; break; case 2: num = a - b; q = a + "-" + b; break; case 3: num = a * b; q = a + "*" + b; break; default: num = a / b; q = a + "/" + b; break; } int B = random.nextInt(4) % (4 - 1 + 1) + 1; switch (B) { case 1: num = num + c; q = q + "+" + c; break; case 2: num = num - c; q = q + "-" + c; break; case 3: num = num * c; q = q + "*" + c; break; default: num = num / c; q = q + "/" + c; break; } //4个运算数的情况,则产生三个运算符 ...... //5个运算数的情况,则产生四个运算符 ...... }
这部分代码是我个人觉得非常精彩的一段代码。虽然仅仅只是用了一个if语句,再加上几个switch语句,但是却完美的实现了运算数和运算符的随机选择。通过一个if语句,选择运算数的个数,然后再通过一个switch语句在“+”、“-”、“*”、“”四个运算符随机选择,有几个运算符,就写几个switch片段。这简简单单的语法,却实现了这么复杂的功能,我突然体会到java语言的魅力,代码的魅力,编程的魅力!
上面的代码,只列出了三个运算数,也就是两个运算符的情况。不过当我写到五个运算符的情况的时候我不禁想到一个问题,我的项目的可扩展性怎么样。不错,确实是这么个问题,虽然整个代码的逻辑非常清晰易懂。但是整个程序的可扩展性不强。但是经过一番思考,我觉得代码的可扩展性还是可以有所改进,因为每个switch代码段的内容都是一样的,所以可以将那一整段代码封装起来,再利用一个for循环,直接就可以达到我们运算符随机生成的目的,这样一来,无论要生成多少个运算符的运算式,我们都可以轻松实现。
3、片段三
ScriptEngine jse = new ScriptEngineManager().getEngineByName("JavaScript"); String hush = String.valueOf(jse.eval(q)); Double result = Double.parseDouble(hush);
这段代码也是我同样非常喜欢的一段代码,虽然仅仅用了三行语句,但是却解决了一个非常关键的问题,那就是运算符优先级的问题。其实我一开始是想用num去保存运算式的结果,这在我的片段二代码中可以清晰的体现。但是后来发现不可以,因为那样子的话,就忽律了运算符优先顺序的问题,从而使得题目的结果都是错误的(这是一个很严肃的问题!哈哈哈哈哈哈哈哈...)。后来我找到了这个eval()函数,利用它可计算某个字符串,并执行其中的的 JavaScript代码的功能从而得到正确的运算式结果result。而且刚刚好,我在前面已经记录并且表示出了运算式字符串,也就是q,所以这里直接引用即可!
七、项目总结
-
编程前:我以模块化的思想去设计,打算将文件生成,运算式产生分成两部分去写,并且我知道最总要的部分还是运算式的产生部分,因为会涉及到较多的算法逻辑,也是整个项目的核心块。
-
编程过程中:我又把文件生成和运算时的产生两部分放到了File类中,然后再单独写一个Main函数。通过这个Main函数去调用File类中的方法。从而实现模块与模块之间的联系。同时又加强了项目的可读性、可扩展性和可移植性。
-
测试阶段(代码复审):我以模块去测试,一个一个模块的检查,测试,通过,最后连接在一起,项目跑起来,运行成功的那一刹那,我无比的激动和兴奋,那种感觉是前所未有的。