zoukankan      html  css  js  c++  java
  • 2016012078 小学四则运算练习软件项目报告

    Coding.net原码仓库地址https://git.coding.net/Meloody/Code.git

    一、需求分析(前提

      话说需求分析是一份体力活儿,也是一份技术活儿,它既是人际交往的艺术,又是逻辑分析与严密思考的产物。

      具有理解能力、设计能力是分析需求必备的技能。一个UML图可以清晰地展示出需求分析。(参考博客1)但考虑此次作业涉及领域相对较小,客户需要一个四则运算的命令行软件,所以不采用UML图。

      

    • 输入 n 人性化地决定生成题目的个数

    • 运算符为:+-*÷

    • 每个数字在0-100之间,运算符3-5个

    • 每个练习题至少要包含2种运算符

    • 运算过程中不出现负数与非整数

    • 设置参数控制生成题目数量、运算符个数、计算数产生范围

    • 使用jdk8u161版本
    • 使用jre8u161版本
    • 学号与题及其正确答案输出到文件“result.txt”

      

    二、功能设计(价值)

    基本功能:

    • 实现四则运算出题,随机生成运算式。

    扩展功能:

    • 计算分数的加减法  例如:16/5-7/3/7*3/5 = 3

    • 运算式去重

     

    三、设计实现(保障

    设计包括你会有哪些类,这些类分别负责什么功能,他们之间的关系怎样?你会设计哪些重要的函数,关键的函数是否需要画出流程图?函数之间的逻辑关系如何?

      

    设计包括的类及负责的功能,他们之间的关系:

    (一) 整体上,project项目/src下建立Main.java和WriteToFile.java文件。

    (二) 共包含两个类文件,Main为主函数,主要负责实现产生随机四则运算,WriteToFile为辅助类,解决文件读写问题。

     

    涉及重要函数和函数流程图及逻辑关系:

     (一) 导入类和函数:

    int random(int x) 随机数类,这个类的实例用于生成伪随机数流

     

    import java.util.Random;
    

      

    ArrayList(),有序可重复的集合,其底层就是数组

     

    import java.util.ArrayList;
    

     

    Scanner,属于SDK1.5新增的一个类,可是使用该类创建一个对象

    import java.util.Scanner;
    

    BufferedWriter,缓冲字符输出流,继承于Writer,为其他字符输出流添加一些缓冲功能

    import java.io.BufferedWriter;
    

    FileWriter,字符流,用法和FileInputStream和FileOutputStream一样,只是应用的范围不一样(文件字符流用于纯文本文件的读写比较快速)

    import java.io.FileWriter;
    

    printStackTrace(),是打印出异常,还显示调用信息

    e1.printStackTrace();
    

    eval()函数,计算字符串内的运算式

     ax=ax+"="+jse.eval(ax);
    

    (二) 流程图如下:

     

     

    四、算法详解(灵魂

    (一) 当被除数不能整除除数时,随机生成能够整除的除数

    private static int dec(int x,int y){
    		Random random=new Random();
    		if(x%y!=0)//
    { y=random.nextInt(100)+1; return dec(x,y); } else{ return y; } }

    (二) 通过javascript的eval函数计算字符串内的运算式

       /**
    	 * 计算等式结果,返回运算式
    	 */
    	static ScriptEngine jse = new ScriptEngineManager().getEngineByName("JavaScript");  
    	private static ArrayList<String> calculate(ArrayList<String> arrayList){
    		ArrayList<String> ArithExpress=new ArrayList<String>();
    		for(String ax:arrayList){
    			try {
    				ax=ax+"="+jse.eval(ax);
    				System.out.println(ax);
    				ArithExpress.add(ax);
    			} catch (ScriptException e) {
    				// TODO Auto-generated catch block
    				e.printStackTrace();
    			}
    		}
    		return ArithExpress;
    	}
    

     

    (三)逆波兰式(相似调度场)的求值:

    逆波兰式式也称后缀表达式。一般求值表达式都是中缀表达式,而后缀表达式求值更容易,所以将中缀表达式转换为后缀表达式。

       

      步骤:

      第一步:将中缀表达式转换为后缀表达式。 

              1,将+,-,*,/,(等要用到的运算进行优先级处理)

              2,需要用到一个符号栈处理:

                  a,数字字符,小数点直接存入另一个字符串S。

                  b, "( "直接入栈,当表达式str字符中")"出现,将所有栈顶运算符转移至S直至遇到"(","( "出栈。

                  c,当表达式str字符中的运算符优先级大于等于(注意一定要大于等于)栈顶运算符优先级时,将字符依次存入S,直至小于为止。当前运算符入栈。

                  d,最后当str访问完,将栈中剩余运算符存到S。

     

      第二步:将后缀表达式求值。

              1,需要一个浮点型栈(具体根据题目要求)存放数值。

              2,遍历S遇见操作数,小数点时处理后入栈,直至遇见运算符,出栈两个操作数,处理后将结果再入栈。

              3,栈顶元素即为表达式解。

     

       eg. 根据后缀表达式:12 34 11 99 * / - 计算结果。
        从左到右遍历后缀表达式,
        遇到数字就进栈,
        遇到符号,就将栈顶的两个数字出栈运算,运算结果进栈,直到获得最终结果。

      1 import java.util.ArrayList;
      2 import java.util.Iterator;
      3 import java.util.List;
      4 import java.util.Stack;
      5 
      6 /*
      7  *测试 -5*(10/(2*4.5-4)+(-3/1.5+4)*(-2))/(-2/1-(-1))+12=2.0
      8  */
      9 public class Calc {
     10     // 求四则运算表达式运算结果
     11     public static double excute(String value) throws Exception {
     12         List<String> list = toList(value);// 按顺序转成数字符号list 即中序表达式
     13         list = toSuffixExpressionList(list);// 转成逆波兰式数字符号list 即后序表达式
     14         double result = suffix_excute(list);// 求逆波兰式结果
     15         System.out.println(value + "=" + result);
     16         return result;
     17     }
     18 
     19     // 表达式划分成中序list 即从左到右数字符号分开
     20     private static List<String> toList(String value) {
     21         // 开始为-时加上0
     22         if ("-".equals(value.substring(0, 1))) {
     23             value = "0" + value;
     24         }
     25         int begin = 0;
     26         int end = 0;
     27         String item;
     28         List<String> resultList = new ArrayList<String>();
     29         for (int i = 0, len = value.length(); i < len; i++) {
     30             item = value.substring(i, i + 1);
     31             if (isOperator(item)) {
     32                 // 负数跳过
     33                 if ("-".equals(item) && "(".equals(value.substring(i - 1, i))) {
     34                     continue;
     35                 }
     36                 end = i;
     37                 // 前一个非符号时加上数字
     38                 if (begin != end) {
     39                     resultList.add(value.substring(begin, end));
     40                 }
     41                 // 加上符号
     42                 resultList.add(value.substring(end, end + 1));
     43                 begin = end + 1;
     44             }
     45         }
     46         // 加上最后一个数字
     47         if (begin != value.length()) {
     48             resultList.add(value.substring(begin));
     49         }
     50         // System.out.println(value + "=" + list);
     51         return resultList;
     52     }
     53 
     54     // 中序list转换成逆波兰式list 左右根
     55     private static List<String> toSuffixExpressionList(List<String> list) throws Exception {
     56         Stack<String> operatorStack = new Stack<String>();// 符号栈
     57         Stack<String> resultStack = new Stack<String>();// 结果栈
     58         Iterator<String> iter = list.iterator();
     59         while (iter.hasNext()) {
     60             String item = iter.next();
     61             if (isOperator(item)) {
     62                 if (")".equals(item)) {
     63                     // 遇到)时符号栈一直弹出并压入结果栈直到遇到(,弹出(废弃,结束。
     64                     while (!(operatorStack.isEmpty() || "(".equals(operatorStack.peek()))) {
     65                         resultStack.push(operatorStack.pop());
     66                     }
     67                     // 弹出(
     68                     if (!operatorStack.isEmpty() && "(".equals(operatorStack.peek())) {
     69                         operatorStack.pop();
     70                     } else {
     71                         throw new Exception("(少了");
     72                     }
     73                 } else if ("(".equals(item)) {
     74                     // 遇到(时直接入符号栈,结束
     75                     operatorStack.push(item);
     76                 } else {
     77                     // 遇到其他运算符时与符号栈顶(若符号栈顶为空或(时直接入符号栈,结束)运算比较 若比栈顶高直接入符号栈,结束
     78                     // 否则符号栈弹出并压入结果栈 并再执行与符号栈顶比较直到弹入符号栈,结束
     79                     while (!(operatorStack.isEmpty() || "(".equals(operatorStack.peek()))) {
     80                         if (compareOperator(item, operatorStack.peek()) < 1) {
     81                             resultStack.push(operatorStack.pop());
     82                         } else {
     83                             break;
     84                         }
     85                     }
     86                     operatorStack.push(item);
     87                 }
     88 
     89             } else {
     90                 // 数字时直接入结果栈
     91                 resultStack.push(item);
     92             }
     93         }
     94         // 符号栈全部弹出并压入结果栈
     95         while (!operatorStack.isEmpty()) {
     96             if ("(".equals(operatorStack.peek())) {
     97                 throw new Exception("(多了");
     98             }
     99             resultStack.push(operatorStack.pop());
    100         }
    101         // 结果栈弹出并反序得出最终结果
    102         iter = resultStack.iterator();
    103         List<String> resultList = new ArrayList<String>();
    104         while (iter.hasNext()) {
    105             resultList.add(iter.next());
    106         }
    107         // System.out.println(list + "=" + rtList);
    108         return resultList;
    109     }
    110 
    111     // 逆波兰式list 求值
    112     // 从左至右扫描表达式,遇到数字时,将数字压入堆栈,遇到运算符时,弹出栈顶的两个数,用运算符对它们做相应的计算(次顶元素 op
    113     // 栈顶元素),并将结果入栈;重复上述过程直到表达式最右端,最后运算得出的值即为表达式的结果。
    114     private static double suffix_excute(List<String> list) {
    115         Stack<Double> resultStack = new Stack<Double>();
    116         Iterator<String> iter = list.iterator();
    117         Double num1;
    118         Double num2;
    119         while (iter.hasNext()) {
    120             String item = iter.next();
    121             if (isOperator(item)) {
    122                 num2 = resultStack.pop();
    123                 num1 = resultStack.pop();
    124                 resultStack.push(doOperator(num1, num2, item));
    125             } else {
    126                 resultStack.push(Double.parseDouble(item));
    127             }
    128         }
    129         return resultStack.pop();
    130     }
    131 
    132     // 比较两运算高低 1 1>2, 0 1=2 -1 1<2
    133     private static int compareOperator(String operator1, String operator2) {
    134         if ("*".equals(operator1) || "/".equals(operator1)) {
    135             return ("-".equals(operator2) || "+".equals(operator2)) ? 1 : 0;
    136         } else if ("-".equals(operator1) || "+".equals(operator1)) {
    137             return ("*".equals(operator2) || "/".equals(operator2)) ? -1 : 0;
    138         }
    139         // 这个到不了
    140         return 1;
    141     }
    142 
    143     // +-*/基本运算
    144     private static double doOperator(Double num1, Double num2, String operator) {
    145         if ("+".equals(operator)) {
    146             return num1 + num2;
    147         } else if ("-".equals(operator)) {
    148             return num1 - num2;
    149         } else if ("*".equals(operator)) {
    150             return num1 * num2;
    151         } else if ("/".equals(operator)) {
    152             return num1 / num2;
    153         }
    154         // 这个到不了
    155         return -1;
    156     }
    157 
    158     // 是否为运算符
    159     private static Boolean isOperator(String value) {
    160         return "(".equals(value) || ")".equals(value) || "+".equals(value) || "-".equals(value) || "*".equals(value)
    161                 || "/".equals(value);
    162     }
    163 
    164     public static void main(String[] args) {
    165         try {
    166             excute("-5*(10/(2*4.5-4)+(-3/1.5+4)*(-2))/(-2/1-(-1))+12");
    167         } catch (Exception e) {
    168             e.printStackTrace();
    169         }
    170     }
    171 }
    View Code

    参考一看就懂的大神博客2

    (四)设置参数控制生成运算符个数、计算数产生范围:

    for(int i=0;i<num;i++){
                    int n=random.nextInt(3)+3; //3-5个运算符
                    int[] number=new int[n+1]; 
                    String ex=new String();
                    for(int j=0;j<=n;j++){
                        number[j]=random.nextInt(100)+1; //4-5个数字
                    }
    

     

    (五)调用ScriptEngine脚本:

    static ScriptEngine jse = new ScriptEngineManager().getEngineByName("JavaScript"); 
    try {
                BufferedWriter bw = new BufferedWriter(new FileWriter(path));
                bw.write("2016012078");  
                bw.newLine();
                for(String con:content){
                    bw.write(con);  
                    bw.newLine();
                }
                bw.close();
            } catch (IOException e1) {
                // TODO Auto-generated catch block
                e1.printStackTrace();//printStackTrace()方法是打印出异常,还显示调用信息.
            }
    

    五、测试运行(成果)

    程序的运行截图展示实现功能满足项目需求:

     

    (一)运行环境jdk-8u161-windows-x64.exe;

    输入java、javac、java -version,java -version回车会返回三行信息如下(第一行表Java语言的语法版本):

    (二)命令行测试(清晰步骤见图):

     注意我在测试的时候发现命令行测试会出现gbk编码错误,所以要输入:javac -encoding utf-8 Main.java

    (三) result.txt文件结果:

    (四) Console运行结果:

     

    六、满意代码(精彩)

    (一) 随机产生num个运算式,将产生的运算式存入List集合:

    char[] operator=new char[]{'+','-','*','/'};
    			Random random=new Random();
    			ArrayList<String> expression=new ArrayList<String>();
    			for(int i=0;i<num;i++){
    				int n=random.nextInt(3)+3; //3-5个运算符
    				int[] number=new int[n+1]; 
    				String ex=new String();
    				for(int j=0;j<=n;j++){
    					number[j]=random.nextInt(100)+1; //4-5个数字
    				}
    				for(int j=0;j<n;j++){
    					int s=random.nextInt(4);//随机选择某运算符
    					ex+=String.valueOf(number[j])+String.valueOf(operator[s]);
    					if(s==3){number[j+1]=decide(number[j],number[j+1]);}
    				}
    				ex+=String.valueOf(number[n]);
    				expression.add(ex);
    			}
    

    (二)整型转化为字符型时(Bug调试5小时),借助double类型:

     double result=0;
                    try {
                         result = Double.parseDouble(String.valueOf(jse.eval(ex)));
                    } catch (ScriptException e) {
                        e.printStackTrace();
                    }
                    if(Math.floor(result)==result&&result>0&&result<99999) {
                         System.out.println(ex + "=" + (int) result);
                         expression.add(ex);
                    }
                    else i--;                
                }
    

    三)下面的方法为分式化解以及计算分数加减乘除:

    public static String sim(int a,int b){
            int y = 1;
            //求分子分母的最小公因数
            for(int i=a;i>=1;i--){
                if(a%i==0&&b%i==0){
                    y = i;
                    break;
                }
            }
            int z = a/y;
            int x = b/y;
            if(z==0)//分子为0则结果为0
    { return "0"; } return ""+z+"/"+x; }

     ProperFraction类是分数类继承Operand类,包含分子,分母属性。加减乘除,约分,计算等方法。

    public static ProperFraction cal(ProperFraction a,
                                                   ProperFraction b, Symbol s){
        ProperFraction p = null;
        //计算分数加减乘除
        switch (s.getValue()){
            case "+":p = fracAdd(a.numa,a.numb,b.numa,b.numb);break;
            case "-":p = fracSub(a.numa,a.numb,b.numa,b.numb);break;
            case "*":p = fracMul(a.numa,a.numb,b.numa,b.numb);break;
            case "/":p = fractDiv(a.numa,a.numb,b.numa,b.numb);break;
        }
        return p;
    }
    

    代码去重,程序一次运行生成的题目不重复

    任何两道题目不能通过有限次交换+和×左右的算术表达式变换为同一道题目。例如,56+ 45 = 和45 +56 = 是重复的题目,6 × 8 = 和8 × 6 = 也是重复的题目。在后期我会完善这个功能。

    七、总结反思(提升)

     

      模块化这个词最早出现在研究工程设计的《Design Rules》中,模块化是以分治法为依据。

      简单说就是把软件整体划分,划分后的块组成了软件。我设计的程序通过需求分析构架软件、分析功能、运用类及函数,大而化小,逐步实现软件设计。Bug调试,我依旧坚持挺过来了,还记得最清楚的一个Bug,整型转化为字符型的一个小细节,花了5小时左右。

     

      这次项目,是一次很大的锻炼,我尝试解决平时没有遇到的问题,遇到问题会焦急。但是,看着自己解决了一个个模块,我也异常兴奋。这次项目,从最初的分析设计,中期编码,到后期调试,整个过程可以说,每一步都充满了挑战,但我相信艰辛过后就是甜蜜的幸福。我做到了,见到彩虹,依然微笑着。

      

      经验上,下次做项目,我会更加从大处着手,小细节交给后期,高效率也是一门艺术。

      

    八、PSP展示(记录)

     

    任务内容

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

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

    Planning

    计划

    400

    811

    ·        Estimate

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

    2天

    4天

    Development

    开发

    375

    800

    ·        Analysis

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

    70

    150

    ·        Design Spec

    ·         生成设计文档

    20

    15

    ·        Design Review

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

    3

    5

    ·        Coding Standard

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

    8

    10

    ·        Design

    ·         具体设计

    10

    20

    ·        Coding

    ·         具体编码

    200

    200

    ·        Code Review

    ·         代码复审

    4

    30

    ·        Test

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

    60

    360

    Reporting

    报告

    10

    0

    ·         Test Report

    ·         测试报告

    3

    0

    ·         Size Measurement

    ·         计算工作量

    2

    1

    ·         Postmortem & Process Improvement Plan

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

    10

    10

      问题:这个环节重要的是让自己看到自己的估计和实际消耗时间,哪个环节耗时最多,哪个环节估计和实践相差巨大?为什么?

      

      估计和实际的总时间相差了近一倍,主要原因是代码能力不足,好好反思后,继续多多编程,认真写项目。在测试(自我测试,修改代码,提交修改)阶段耗时最多,同时也是和估计差距最大的一个环节,主要是因为自身对Bug调试的主观回避,但是这次之后,我会客观估计时间,做出更加合理的计划,对项目开发有一个更深刻的认识。

       我还有分享的就是我做项目的心情状态,我承认这个过程不是三言两语就可以让承受地压力风轻云淡,但是我始终有信念,任何一件事,花出时间和实践就会有收获!

        

    1. 我们应该怎么做需求分析 https://www.cnblogs.com/mengfanrong/p/4594710.html

    2.  java四则运算-通过逆波兰式求值http://tianyami.iteye.com/blog/1462498

    雪儿言
  • 相关阅读:
    OCP-1Z0-051-V9.02-108题
    以一种访问权限不允许的方式做了一个访问套接字的尝试
    常用的几个官方文档
    OCP-1Z0-053-V12.02-388题
    FusionChart实现奇偶间隔显示
    OCP-1Z0-053-V12.02-138题
    Tcp and Udp NAT 穿越穿透打洞
    flashback database实验(使用lgmnr找出还原时间点)
    flashback table实验(基于undo数据)
    flashback query实验(基于undo数据)
  • 原文地址:https://www.cnblogs.com/weixq351/p/8626838.html
Copyright © 2011-2022 走看看