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

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

 

项目克隆地址:https://git.coding.net/chenf640/workhome2_2.git

目录:

一、需求分析

二、功能设计

三、设计实现

四、算法详解

五、测试运行

六、代码展示

七、psp

八、总结

——————————————————————————————————————————————————

一、需求分析

(一)功能需求

 基本功能:

  • 程序可接收一个输入参数n,然后随机产生n道加减乘除(分别使用符号+-*÷来表示)练习题。
  • 每个数字在 0 和 100 之间,运算符在3个到5个之间;
  • 每个练习题至少要包含2种运算符;
  • 所出的练习题在运算过程中不得出现负数与非整数,比如3÷5+2=2.6,2-5+10=7等是不合法的;
  • 练习题生成好后,将你的学号与生成的n道练习题及其对应的正确答案输出到文件“result.txt”中,不要输出额外信息,文件目录与程序目录一致;
  • 当程序接收的参数为4时,以下为一个输出文件示例。

2018010203

13+17-1=29

11*15-5=160

3+10+4-16=1

15÷5+3-2=4

扩展功能 

  • 支持有括号的运算式,包括出题与求解正确答案。
  • 扩展程序功能支持真分数的出题与运算(只需要涵盖加减法即可),例如:1/6 + 1/8 + 2/3= 23/24。注意在实现本功能时,需支持运算时分数的自动化简,比如 1/2+1/6=2/3,而非4/6,且计算过程中与结果都须为真分数。

(二)程序需求

1、只能使用Java语言。

2、生成文件时请使用相对路径,生成的txt 文件需在项目的根目录下,可直接查看演示示例。

3、使用的JDK版本为 jdk8u161,使用的JRE版本为jre8u161。

4、不得使用除限定版本jdk与jre外的额外依赖包。

 

二、功能设计

(一)基本功能

1、保证输入的是数字或者特定字符,如果不符合要求,会有提示,并且可以重新输入。

2、当输入一个参数n,随机产生n道加减乘除算术题。保证生成的每个练习题至少要包含2种运算符。且保证在算数过程中,不出现负数,和小数。

3、将生成的n道练习题及其对应的正确答案输出到文件“result.txt”中,不要输出额外信息,文件目录与程序目录一致;

4、为保证随机产生的四则运算更满足用户的需求,当输入参数n决定产生四则运算的个数时,另外输入一个参数m,决定产生的n道四则运算包含运算符的个数(3、4、5)  

(二)扩展功能

1、保证生成的分数运算,在运算过程中不产生假分数。并且实现自动化简。

2、产生带有括号的运算式时保证不产生没有意义的括号。

三、设计实现

该程序我的设计思想是一个类实现一个功能,提高代码的重用性,减少冗杂度。让代码更清晰。

(一)Main类

功能:是主函数,包含 public static void main(String[] args) 方法。并且包含判断 输入的参数是否为整数的 函数,如果不是函数,可提示用户重新输入。

目的:确保用户输入满足要求的整数的参数(产生四则运算数量1-1000,每个四则运算包含运算符3-5)

(二)Algorithm类

功能:产生符合要求的一个四则运算,不产生括号。该类中包含一个  带参的返回类型为字符串的函数。该函数参数为产生四则运算中包含的符号个数,可以产生包含任意多符号数量的四则运算,在该函数中,为满足用户需求,运算符数量可以为三、四、五,即该函数参数可以为3、4、5。

目的:产生满足用户需求的四则运算。(包含的参数个数、在运算过程中不能产生负数、小数)

关系:公有类,可直接调用。

(三)BracketsAlgo类

功能:产生符合要求的带有括号的四则运算该类中包含一个  带参的返回类型为字符串的函数。该函数参数为产生四则运算中包含的符号个数,可以产生包含任意多符号数量的四则运算,在该函数中,为满足用户需求,运算符数量可以为三、四、五,即该函数参数可以为3、4、5。

目的:产生括号,增大运算难度。

(四)Fraction类

功能:生成分数运算,保证在运算过程中不产生假分数。并且实现自动化简。该函数参数为产生四则运算中包含的符号个数,可以产生包含任意多符号数量的四则运算,在该函数中,为满足用户需求,运算符数量可以为三、四、五,即该函数参数可以为3、4、5。

目的:练习分数运算。

(五)CreatFile类 

功能:生成规定数量的四则运算,并将运算式与结果输出到result.txt文件中。该类中包含一个带参无返回的函数,该函数参数为要打印四则运算的数量。
目的:将符合要求的四则运算输出到txt文件中,方便保留与查看。
关系:公有类,可直接调用。

(六)Symbol类

功能:将产生的代表运算符的数字转换为对应的运算符。(0代表“+”,1代表“-”,2代表“*”,3代表“÷”)
目的:输出符合规范的运算符

(七)Test测试类

 功能:测试方法。

 

四、算法详解(具体代码分析)

 (一)重要算法

1.Algorithm类中的Algorithm算法

算法功能:产生,符合要求的四则运算(运算过程中不产生负数、小数,每个四则运算至少包括两种运算符)

算法思想

1.1.判断产生的运算符不完全相等(至少包括两种)。

  • 用while循环,如果随机产生的数字完全相等,则一直循环,直到不完全一致。
  • 用数字0-3分别代表运算符的加减乘除,将随机生成的数字进行比较,如果四个数字完全相等,则重新生成,最终将满足要求的代表运算符的数字存到数组中。

1.2.运算符之间优先级比较。

  • 将运算符从前到后两两比较,先计算优先级高的运算符,先乘除后加减,同级运算符从左向右依次计算。
  • 通过运算符两两比较,每循环一次就将生成的结果代替原来的数字,循环结束后,最终的结果会放到数组的最后一位。在计算过程中判断是否产生负数、是否产生小数,是否除数为0等,如果产生不满足要求的运算式,则舍去,重新开始。

以一次循环为例:这是不完整的流程图,只包含其中一部分情况。

1.3 .产生包含任意多运算符的四则运算。

  • 只需改变循环的次数

1.4.将生成的正确的四则运算和结果放到一个字符串中,返回该字符串给 CreatFilel类中的写入文件方法。

  • 为了避免在比较优先级的循环中,改变存放数组与运算符的数组,因此,在进行循环判断前,将运算数与运算符交错放到数组中,如果通过验证证明该四则运算符合要求,则返回给 CreatFilel类中的写入文件方法,写入result.txt文件。
  • 在输出时,用for进行判断,如果是运算数子部分则直接输出,如果是运算符部分,则将数字转化为对应的运算符号进行输出。

如图所示:

 

 

2.CreatFile中的creatFile将生成结果写入指定文件

  •  将返回的字符串写入指定路径下的result.txt文件中,并在该文件第一行输出学号。
  • 该算法比较简单,需要导入java.io这个jar包。
  • 通过main方法调用,传入两个参数,生成算法的数量和每个算法包含的运算符数量。

3.Fraction类中的fractionArithmetic算法

算法功能:生成符合要求的分数运算式。

算法实现:

  • 随机产生随机数字(0、1)来代表运算符(+、-)
  • 随机产生两个数字,一个做分母、一个做分子(不能为0)。在运算过程中,不断判断产生的分数是否大于等于一、并不断将分数进行化简。

(二)其他算法

1.Symbol方法中的symbol算法

算法思想:输出运算符时,将数字与字符一一对应。用switch()语句即可完成。

2.验证输入的为符合要求的整数(包括生成四则运算的数量、生成的四则运算中包含运算符的数量)

算法思想:将输入的内容进行验证,先验证是否为整数,如果是整数,再验证该整数是否符合要求。若不符合要求,则对用户进行提示,要求重新输入

在命令行结果如下:

3.BracketsAlgo类中的bracketsAlgo算法

算法功能:产生带有括号的符合要求的四则运算

算法实现:

  • 将随机生成的运算符从左向右两两比较。如果前面的优先级高,则进行运算,保存结果;如果前面运算符的优先级低,则加上括号。
  • 该算法一定是从左向右依次运算。

缺点:该算法可以生成带有括号的四则运算,但产生括号的数量不能确定。

 五、测试运行

为了更好地实现用户的要求,在运行代码时需要输入你要打印的题目的具体数量,程序会随机产生包含3-5个运算符的符合要求的四则运算式(不带括号、带括号、分数)。

1.进入src文件夹;

2.在命令行输入javac -encoding utf-8 Main.java;

3.回车再输入java Main 20;

回车.result.txt文件生成,对应的运算式写入result.txt文件

 运算结果:
文件内容(一半是带括号的 、一半是分数的):
 

六、代码展示:

Algorithm类中的Algorithm算法:

       /* 将运算符从前到后两两比较,先计算优先级高的运算符,先乘除后加减,同级运算符从左向右依次计算
              在计算过程中判断是否产生负数、是否产生小数,是否除数为0等,如果产生不满足要求的运算式,
              则舍去,如果符合要求,则打印出来。
             */
            for (int i = 0; i < m.length; i++) {
                if ((m1[i] < m1[i + 1]) && (m1[i + 1] == 2)) {
                /* 包含情况为 0<2;1<2 计算优先级高的(乘法) 区域运算,并将结果放到 存放数字的数组中,运算过的运算符也被取代*/
                    num[i + 2] = num[i + 1] * num[i + 2];
                    num[i + 1] = num[i];
                    m1[i + 1] = m1[i];
                    number++;
                } else if ((m1[i] < m1[i + 1]) && m1[i + 1] == 3 && m1[i] != 2) {
                /* 包含情况为 0<3;1<3计算优先级高的(除法) 区域运算,并将结果放到 存放数字的数组中,运算过的运算符也被取代*/
                    if (num[i + 2] != 0) {              //首先除数不能为0
                        int a = num[i + 1] % num[i + 2];  //是否整除
                        if (a == 0) {                  //如果整除,进行除法运算
                            num[i + 2] = num[i + 1] / num[i + 2];
                            num[i + 1] = num[i];
                            m1[i + 1] = m1[i];
                            number++;
                        } else {                     //如果不能整除,重新进行四则运算的产生
                            number = 0;
                            for (int k = 0; k < m.length; k++) {
                                m[k] = 0;
                            }
                            break;
                        }
                    } else {                       //除数为0,重新进行四则运算的产生
                        number = 0;
                        for (int k = 0; k < m.length; k++) {
                            m[k] = 0;
                        }
                        break;
                    }
                } else if (m1[i] == 2 && m1[i + 1] == 3) {
                 /* 包含情况为 2<3计算从左向右进行,并将结果放到 存放数字的数组中*/
                    m[i + 1] = m[i] * m[i + 1];
                    number++;
                } else if ((m1[i] < m1[i + 1]) && (m1[i + 1] == 1)) {
                 /* 包含情况为 0<1  计算从左向右进行,并将结果放到 存放数字的数组中*/
                    m[i + 1] = m[i] + m[i + 1];
                    number++;
                } else if ((m1[i] > m1[i + 1]) && m1[i] == 3) {
                /* 包含情况为 3>2;3>1;3>0  计算从左向右进行,并将结果放到 存放数字的数组中*/
                    if (num[i + 1] != 0) {              //被除数不能为0
                        int a = num[i] % num[i + 1];
                        if (a == 0) {                 //能够整除
                            num[i + 1] = num[i] / num[i + 1];
                            number++;
                        } else {
                            number = 0;
                            for (int k = 0; k < m.length; k++) {
                                m[k] = 0;
                            }
                            break;
                        }
                    } else {                      //被除数为0
                        number = 0;
                        for (int k = 0; k < m.length; k++) {
                            m[k] = 0;
                        }
                        break;
                    }
                } else if ((m1[i] > m1[i + 1]) && m1[i] == 2) {
                /* 包含情况为 2>1;1>0  计算从左向右进行,并将结果放到 存放数字的数组中*/
                    num[i + 1] = num[i] * num[i + 1];
                    number++;
                } else if ((m1[i] > m1[i + 1]) && m1[i] == 1) {
                /* 包含情况为 1>0  计算从左向右进行,并将结果放到 存放数字的数组中*/
                    if (num[i] > num[i + 1]) {     //是否产生负数
                        num[i + 1] = num[i] - num[i + 1];
                        number++;
                    } else {
                        number = 0;
                        for (int k = 0; k < m.length; k++) {
                            m[k] = 0;
                        }
                        break;
                    }
                } else {
                /* 包括情况为0==0;1==1;2==2  3==3*/
                    if (m1[i] == m1[i + 1]) {
                        if (m1[i] == 0) {       //加法
                            num[i + 1] = num[i] + num[i + 1];
                            number++;
                        } else if (m1[i] == 1) {  //减法,判断是否产生负数
                            if (num[i] > num[i + 1]) {
                                num[i + 1] = num[i] - num[i + 1];
                            } else {
                                number = 0;
                                for (int k = 0; k < m.length; k++) {
                                    m[k] = 0;
                                }
                                break;
                            }
                        } else if (m1[i] == 2) {    //乘法
                            num[i + 1] = num[i] * num[i + 1];
                            number++;
                        } else {                //除法运算,判断除数不能为0,不能产生小数
                            if (num[i + 1] != 0) {
                                int a = num[i] % num[i + 1];
                                if (a == 0) {
                                    num[i + 1] = num[i] / num[i + 1];
                                    number++;
                                } else {
                                    for (int k = 0; k < m.length; k++) {
                                        m[k] = 0;
                                    }
                                    number = 0;
                                    break;
                                }
                            } else {
                                for (int k = 0; k < m.length; k++) {
                                    m[k] = 0;
                                }
                                number = 0;
                                break;
                            }
                        }
                    }
                }
            }

2.判断你要打印的题目数量的个数是否为整数

以打印题目数量为例:

     /**
     * 判断 输入你要打印的题目数量的个数是否为整数
     * @return
     */
    public static int numberTest() {
        Scanner input = new Scanner(System.in);
        int number =0;
        while(number<1||number>1000){
            System.out.print("请输入你要打印的题目数量 (1-1000):");
            String s = input.next();
            while (!(s != null && s.matches("^[0.0-9.0]+$"))) {// [0-9]没办法识别小数,[0.0-9.0]可以识别小数和整数
                System.out.print("请输入正确的题目数量,类型为整数 (1-1000):");
                s = input.next();
            }
            number = Integer.parseInt((s));
        }
        return number;
    }

七、psp:

本次编码并没有严格计算时间,刚开始也没有预测所有用到的时间,下表所填的时间是估计的,可能不是很准确,下次再做作业时会更加准确的记录时间。

PSP2.1

任务内容

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

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

Planning

计划

 

20

·        Estimate

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

 

20

Development

开发

15*60

27*60

·        Analysis

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

 

60

·        Coding Standard

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

 

30

·        Design

·         具体设计

 

60

·        Coding

·         具体编码

 

14.5*60

·        Code Review

·         代码复审

 

60

·        Test

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

 

9*60

Reporting

报告

 

6.5*60

·         Size Measurement

·         计算工作量

 

10

·         Postmortem & Process Improvement Plan

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

 

20

 

写博客

 

6*60

 

 

 

 

 

 

 

 

 

 

 

 

八、总结:

(一)实现软件的'模块化'原则:

  • 对功能进行分析,并分解成若干子任务,每个函数只实现一种功能,并将一类功能放到一个类中。通过函数之间的调用来实现所有功能。
  • 避免代码冗杂,提高代码的重用性跟规范性。

(二)缺点:

虽然实现了作业的基本要求和附加要求,但关于附加功能的第一条完成得不是很好,虽然不会产生无意义的括号,如 (3*4)*5,但是因为符号的产生是随机的 ,所以很难保证括号的数量。而且,自己的代码还是过于冗杂,还有待提高。

(三)优点:

  • 除了附加一的一些问题,本作业的其他要求都以实现。
  • 代码书写规范
  • 有详细地注释
  • 虽是一个基本功能,但是我用了三种方法去写(从刚开始最复杂的多重循环(Arithmetic3类中的Arithmetic_test()方法到逻辑更加严谨的算法(Algorithm类中的algorithm()方法再到最后更加简洁的(BracketAlgo类中的bracketsAlgo()算法)。虽然看上去浪费了很多时间,但是这也让我认识到了算法的强大,逐渐进步。

(四)个人感受

       首先,先谈谈我写这个作业的过程。

       对于这次作业,刚开始我是低估了难度的。我认为只要两天左右的课余时间就可以完成。结果,这次实际花的时间远远超过我预想的。并且,由于刚开始我的不恰当的想法,导致我前面浪费了许多时间。后面又用了一种更加简便的方法去做,成功的实现了基本功能中的所有要求。于是我开始去做附加要求,对于附加要求,刚开始并没有什么想法,于是,我上网查了些资料,看了同学们的一些博客,渐渐的发现附加功能并没有自己想的那么难。可能刚开始写的时候思路比较顺畅,这次附加功能并没有占用我很长时间(敲代码时间的三分之一左右)。最后,就是,写博客啦。写博客对我而言还是挺费时间,大概花了六个小时。终于,个人项目完成啦!

       这次项目,于我而言还挺能难的,也挺费时间的,主要还是自己算法不是很好,不能找到最优解,导致自己浪费了一些时间。通过这次项目,我收获最大的就是互相学习、交流的重要性。如果没有看同学们、看网上的博客,没有跟同学们讨论,我可能还会限制在自己的思路中。还有就是在敲代码前一定要有明确的思路,要不然敲了半天可能会是无用功。这做这个项目过程中,debug也花费了很多时间。  

       虽然这周很忙,最后几天忙到晚上两点多,电脑没电了才睡觉。但是我很喜欢这种专注于一件事的感觉,以前没事的时候会看看剧,聊聊天,但是最近这几天,有时间就是敲代码,有时走在路上也会想怎么更好的实现功能,之间的逻辑是什么。在项目最终完成时,很激动也很有满足感。我想,这就是软件工程的魅力吧。

       最后,依旧一句话结尾。也依旧是  越努力、越幸运!

原文地址:https://www.cnblogs.com/chenf640/p/8630329.html