zoukankan      html  css  js  c++  java
  • 20165315 结对编程练习_四则运算(整体总结)

    20165315 结对编程练习_四则运算(整体总结)

    需求分析

    • 对需求的理解
      • 支持真分数的四则运算
      • 支持多运算符
      • 能多次随机生成n道题目,n由使用者输入,直到使用者选择退出
      • 能够判断正误,错误时能提醒并输出正确答案
      • 能计算出正确率
      • 处理生成题目并输出到文件
      • 完成题目后从文件读入并判题
      • 多语言支持:简体中文, 繁體中文, English
    • 后续拓展的可能
      • 题目去重

    设计思路

    首先要明确这个程序要实现哪些功能,并将这些功能分别写在一个类中:例如计算器写在Calculation中,语言包写在ChooseLanuage中,文件输入输出分别写在InputExpressionOutputExpression等等,接着分别将各个类实现的功能写出来,最后在主类中按顺序调用这些功能,实现四则运算。

    UML图如下:

    实现过程中的关键代码解释

    • 进行带括号的四则运算:需要将输入的字符串更改为后缀式并进行计算。学习了[2016-2017-2 《Java 程序设计》课堂实践项目]之后,发现老师的参考代码MyDC.java,原理是:利用空格作为分隔符将后缀式表达的字符串进行分割,遇到操作数就压栈,遇到操作符就弹出栈顶的两位操作数进行运算,再将运行结果压栈,直到没有下一个分割好的字符串,输出结果:
    import java.util.StringTokenizer;
     import java.util.Stack;
    
     public class MyDC
     {
       /** constant for addition symbol */
       private final char ADD = '+';
       /** constant for subtraction symbol */
       private final char SUBTRACT = '-';
       /** constant for multiplication symbol */
       private final char MULTIPLY = '*';
       /** constant for division symbol */
       private final char DIVIDE = '/';
       /** the stack */
       private Stack<Integer> stack;//存放操作数的栈,且只能存放Integer型
       public MyDC()
       {
         stack = new Stack<Integer>();
       }
    
       public int evaluate (String expr)
       {
         int op1, op2, result = 0;
         String token;
         StringTokenizer tokenizer = new StringTokenizer (expr);//划分表达式
    
         while (tokenizer.hasMoreTokens())
         {
           token = tokenizer.nextToken();//将算数表达式以空格为分隔符进行分解
    
           if (isOperator(token))//见下方isOperateor方法,当是运算符的时候进入if语句
           {
             op2 = (stack.pop()).intValue();
             op1 = (stack.pop()).intValue();//弹出最上面两个操作数
             result = evalSingleOp (token.charAt(0), op1, op2);//见下方evaSingleOp方法
             stack.push (new Integer(result));//将计算结果压栈
           }
           else
             stack.push (new Integer(Integer.parseInt(token)));//操作数入栈
         }
    
         return result;//输出结果
       }
    
       private boolean isOperator (String token)//判断是否为运算符,注意用equal语句比较字符串
       {
         return ( token.equals("+") || token.equals("-") ||
                  token.equals("*") || token.equals("/") );
       }
    
       private int evalSingleOp (char operation, int op1, int op2)
       {
         int result = 0;
    
         switch (operation)
         {
           case ADD:
             result = op1 + op2;
             break;
           case SUBTRACT:
             result = op1 - op2;
             break;
           case MULTIPLY:
             result = op1 * op2;
             break;
           case DIVIDE:
             result = op1 / op2;
         }
    
         return result;
       }
     }
    
    
    • 考虑题目要求为能进行分数运算,想起来教材第四章代码Rational.java可以保留分式进行加、减、乘、除、分数约分等运算,但书上代码

      a. 没有考虑到分母为零或者除数为零的情况,所以加以改动,在此情况下打印错误“分子/除数不能为0并退出运算”;

      b. 分母为负分子为正时的输出没有将符号提前,进行符号提前:

    public class Rational{//有理数
      int numerator=1;//分子
      int denominator=1;//分母
      void setNumerator(int a){//设置分子
        int c=f(Math.abs(a),denominator);//计算最大公约数
        numerator=a/c;
        denominator=denominator/c;
        if (numerator<0&&denominator<0) {
          numerator=-numerator;
          denominator=-denominator;
        }
      }
      void setDenominator(int b){//设置分母
        int c=f(numerator,Math.abs(b));//计算最大公约数
        numerator=numerator/c;
        denominator=b/c;
        if (numerator<0&&denominator<0) {
          numerator=-numerator;
          denominator=-denominator;
        }
        else if (numerator>0&&denominator<0){
            numerator=-numerator;
            denominator=-denominator;
        }
      }
      int getNumerator(){
        return numerator;
      }
      int getDenominator(){
        return denominator;
      }
      int f(int a,int b){//求a,b的最大公约数
        if (a==0) {
          return 1;//c为分母不能为0
        }
        if (a<b) {//令a>b
          int c=a;
          a=b;
          b=c;
        }
        int r=a%b;
        while (r!=0) {
          a=b;
          b=r;
          r=a%b;
        }
        return b;
      }
      Rational add(Rational r){//加法运算
        int a=r.getNumerator();//返回有理数r的分子
        int b=r.getDenominator();//返回有理数r的分母
        int newNumerator=numerator*b+denominator*a;//计算出新分子
        int newDenominator=denominator*b;//计算出新分母
        Rational result=new Rational();
        result.setNumerator(newNumerator);
        result.setDenominator(newDenominator);
        return result;
      }
      Rational sub(Rational r){//减法运算
        int a=r.getNumerator();
        int b=r.getDenominator();
        int newNumerator=numerator*b-denominator*a;
        int newDenominator=denominator*b;
        Rational result=new Rational();
        result.setNumerator(newNumerator);
        result.setDenominator(newDenominator);
        return result;
      }
      Rational muti(Rational r){//乘法运算
        int a=r.getNumerator();
        int b=r.getDenominator();
        int newNumerator=numerator*a;
        int newDenominator=denominator*b;
        Rational result=new Rational();
        result.setNumerator(newNumerator);
        result.setDenominator(newDenominator);
        return result;
      }
      Rational div(Rational r){//除法运算
        int a=r.getNumerator();
        int b=r.getDenominator();
        Rational result=new Rational();
        if (a==0) {
          System.out.println("分母/除数不能为0");
         result.setNumerator(0);
          System.exit(0);
        }
        else{
          int newNumerator=numerator*b;
          int newDenominator=denominator*a;
          result.setNumerator(newNumerator);
          result.setDenominator(newDenominator);
        }
        return result;
      }
    }
    
    
    • 根据MyDC.javaRational.java进行综合与改动,完成代码MyDcRational.java,将整数与小数运算改为分数与整数的后缀式运算:
    import java.util.StringTokenizer;
     import java.util.Stack;
    
     public class MyDcRational
     {
       /** constant for addition symbol */
       private final char ADD = '+';
       /** constant for subtraction symbol */
       private final char SUBTRACT = '-';
       /** constant for multiplication symbol */
       private final char MULTIPLY = '*';
       /** constant for division symbol */
       private final char DIVIDE = '/';
       /** the stack */
       private Stack stack;//存放操作数的栈
       public MyDcRational()
       {
         stack = new Stack();
       }
    
       public Rational evaluate (String expr)
       {
         Rational op1=new Rational();
         Rational op2=new Rational();
         Rational result=new Rational();
         result.setNumerator(0);
         String token;
         StringTokenizer tokenizer = new StringTokenizer (expr);//划分表达式
    
         while (tokenizer.hasMoreTokens())
         {
           token = tokenizer.nextToken();//将算数表达式分解的
    
           if (isOperator(token))//见下方isOperateor方法,当是运算符的时候进入if语句
           {
             op2 = (Rational) stack.pop();
             op1 = (Rational)stack.pop();//弹出最上面两个操作数
             result = evalSingleOp (token.charAt(0), op1, op2);//见下方evaSingleOp方法
             stack.push (result);//将计算结果压栈
           }
           else{
                Rational num=new Rational();
                num.setNumerator(Integer.parseInt(token));//将操作数由string转变为Rational
                stack.push (num);//操作数入栈
           }
    
         }
    
         return result;//输出结果
       }
    
       private boolean isOperator (String token)//判断是否为运算符,注意用equal语句比较字符串
       {
         return ( token.equals("+") || token.equals("-") ||
                  token.equals("*") || token.equals("/") );
       }
    
       private Rational evalSingleOp (char operation, Rational op1, Rational op2)
       {
         Rational result=new Rational();
         result.setNumerator(0);
         switch (operation)
         {
           case ADD:
             result = op1.add(op2);
             break;
           case SUBTRACT:
             result = op1.sub(op2);
             break;
           case MULTIPLY:
             result = op1.muti(op2);
             break;
           case DIVIDE:
             result = op1.div(op2);
             break;
            default:
              System.out.println("Error!");
         }
         return result;
       }
     }
    
    
    • 设立一个栈,存放运算符,首先栈为空;
    • 从左到右扫描中缀式,若遇到操作数,直接输出,并输出一个空格作为两个操作数的分隔符;
    • 若遇到运算符,则与栈顶比较,比栈顶级别高则进栈,否则退出栈顶元素并输出,然后输出一个空格作分隔符;
    • 若遇到左括号,进栈;若遇到右括号,则一直退栈输出,直到退到左括号止。
    • 当栈变成空时,输出的结果即为后缀表达式。

    根据上述思路,完成代码ChangeExpress.java,将前缀式改为后缀式,并且完成分析括号匹配的功能,若左右括号不匹配,输出错误并退出程序运行:

    import java.util.*;
    public class ChangeExpress{
      String originalExpression;
      String changedExpression= "";
      int countLeft=0,countRight=0;
      public void setOriginalExpression(String str){
        originalExpression=str;
      }
      public void changedWay(){
        Stack stackChange=new Stack();//创立栈
        int opValue []=new int[100];
        for (int i=0;i<originalExpression.length() ;i++) {
          char chi=originalExpression.charAt(i);
          if (chi>='0'&&chi<='9'){
              changedExpression=changedExpression+chi;
          }
          else if (chi=='+'||chi=='-'||chi=='*'||chi=='/') {
            changedExpression=changedExpression+" ";//有运算符,数字之间就要有空格,否则是一个整体
            if (stackChange.empty()){//栈为空直接压栈
                stackChange.push(chi);
            }
            else if (judgeValue(chi)>=judgeValue((char)stackChange.peek())) {//运算级别高或者相等压入栈
              stackChange.push(chi);
            }
            else{
              changedExpression=changedExpression+ String.valueOf(stackChange.pop())+" ";//否则直接进入字符串,空格分割运算符
              i--;
            }
          }
          else if(chi=='('){
            countLeft++;
            stackChange.push(chi);//左括号压栈
          }
          else if(chi==')'){
            changedExpression+=" ";
            countRight++;
            while((char)stackChange.peek()!='('){//直到(为止
              changedExpression=changedExpression+ String.valueOf(stackChange.pop())+" ";//弹出栈内东西,空格分割
            }
            stackChange.pop();
          }
        }
        changedExpression+=" ";
        while(!stackChange.empty()){
            changedExpression=changedExpression+String.valueOf(stackChange.pop())+" ";
        }
        if (countLeft!=countRight) {
          System.out.println("括号不匹配");
          System.exit(0);
        }
      }
      public int judgeValue(char c){
        int value=0;
        switch(c){
          case '(':
            value=1;
            break;
          case '+':
          case '-':
            value=2;
            break;
          case '*':
          case '/':
            value=3;
            break;
          case ')':
            value=4;
          default:
            value=0;
        }
        return value;
      }
    }
    
    
    • 最后编写主函数代码Calculation.java,实现功能有:运算式输入、运算、结果输出:
    import java.util.*;
    public class Calculation{
      public static void main(String[] args) {
        Scanner reader=new Scanner(System.in);
        Rational result=new Rational();
        System.out.println("请输入运算式");
        String str=reader.nextLine();
        ChangeExpress change=new ChangeExpress();
        change.setOriginalExpression(str);
        //System.out.println(change.originalExpression);
        change.changedWay();//后缀式化
        //System.out.println(change.changedExpression);
        MyDcRational calculate=new MyDcRational();//后缀式计算
        result=calculate.evaluate(change.changedExpression);
        int a=result.getNumerator();
        int b=result.getDenominator();
        if (b==1){
            System.out.println("result="+a);
        }
        else{
            System.out.println("result="+a+"/"+b);
        }
      }
    }
    
    

    测试方法

    由于编写的绝大部分方法都是void类,故只写了又返回值的方法的测试类

    ChangeExpressTest类:

    JudgeTest类:

    RationalTest类:

    运行过程截图

    • 正常情况(不同语言包)

    • 异常情况(选择语言包时输入错误)

    代码托管地址

    https://gitee.com/BESTI-IS-JAVA-2018/ch1/tree/master/20165315teamwork1/src4

    遇到的困难及解决方法

    • 在编译过程中出现如下问题,提示空值

    解决过程:
    经过单步调试后发现,空值的原因是忘记将void类型的类调用,对仍是空值的变量进行赋值,调用后编译通过。

    • 在解决文件输入输出的问题时,出现了明明电脑以自动生成算式,却无法输出的问题:

    解决过程:
    其实原因同上面的问题,也是忘记调用输出流里的void类型的输出方法...

    • 在运行代码时,发现明明代码中设置了从键盘输入的指令,却没有执行,直接跳过并结束了程序:

    解决过程:
    在尝试修改代码时,我发现是需要在循环设置从键盘输入的指令:

    • 在运行代码时,发现程序的计算结果一直是空值:

    解决过程:
    在检查了程序后发现是没有调用计算器类进行计算:

    • 在编写测试代码时,出了如图的问题:

    解决过程:
    经过调试,我发现原来是float型数据我忘记了数字后面的F,加上后即可以正常测试了

    PSP

    PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
    PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
    Planning 计划
    · Estimate · 估计这个任务需要多少时间 60 60
    Development 开发
    ·Analysis · 需求分析 (包括学习新技术) 70 90
    ·Design Spec · 生成设计文档 45 50
    ·Design Review · 设计复审 (和同事审核设计文档) 60 60
    ·Coding Standard · 代码规范 (为目前的开发制定合适的规范) 30 20
    ·Design · 具体设计 180 210
    · Coding · 具体编码 180 200
    ·Code Review · 代码复审 60 60
    ·Test · 测试(自我测试,修改代码,提交修改) 180 210
    Reporting 报告
    · Test Report · 测试报告 90 120
    · Size Measurement · 计算工作量 20 30
    · Postmortem & Process Improvement Plan · 事后总结, 并提出过程改进计划 30 60
    合计 1005 1170

    对结对的小伙伴做出评价

    这次代码的编写驾驶员是徐雯,我是领航员。徐雯同学这周很忙,参加了很多比赛,大部分时间都在忙比赛的事,但是即便如此,我们的结对编程也没有落下进度,这都是靠着我们对Java学习的热情,彻夜将代码编写并运行了出来,这足以证明我的结对伙伴对学习的认真负责!由于她对于代码实现度的高标准高要求,所以在编写过程中也出了很多小差错,但是这些也帮助我们提升了对代码的敏感度,结对学习使我们编程的效率提高了!

  • 相关阅读:
    特效优化
    Jsp
    JRebel 热部署
    mysql
    行为树
    Medium | LeetCode 139. 单词拆分 | 动态规划
    Medium | LeetCode 31. 下一个排列
    Easy | LeetCode 27. 移除元素 | 快慢指针
    Medium | LeetCode 437. 路径总和 III | 树 + 回溯 + 前缀和
    Medium | LeetCode 337. 打家劫舍 III | 树后序遍历 + 动态规划
  • 原文地址:https://www.cnblogs.com/yh666/p/8911226.html
Copyright © 2011-2022 走看看