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

项目源码地址:https://git.coding.net/Donsparks/project1.git

测试效果见src下生成的result.txt文件

一、需求分析

  1. 程序可从命令行接收一个输入参数n,然后随机产生n道加减乘除练习题。
  2. 每个数字在0和100之间,运算符3个到5个之间。
  3. 每个练习题至少要包含2种运算符。
  4. 所出的练习题在运算过程中不得出现负数与非整数。
  5. 将学号与生成的n道练习题及其对应的正确答案输出到文件“result.txt”中。
  6. 支持有括号的运算式,包括出题与求解正确答案。注意,算式中存在的括号必须大于2个,且不得超过运算符的个数。(附加)
  7. 支持真分数的出题与运算(只需要涵盖加减法即可),且支持分数的自动化简。(附加)

二、功能设计

  1. 基本功能:实现四则运算的功能,并能够将题目输出到文本文件中。
  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类中的方法。从而实现模块与模块之间的联系。同时又加强了项目的可读性、可扩展性和可移植性。

  • 测试阶段(代码复审):我以模块去测试,一个一个模块的检查,测试,通过,最后连接在一起,项目跑起来,运行成功的那一刹那,我无比的激动和兴奋,那种感觉是前所未有的。

八、PSP展示

原文地址:https://www.cnblogs.com/xdhou/p/8641679.html