20162311 结对编程项目-四则运算 整体总结

20162311 结对编程项目-四则运算 整体总结

一、需求分析

  • 支持多运算符
  • 支持真分数
  • 用户可选择生成题目的等级
  • 处理生成题目并输出到文件
  • 完成题目后从文件读入并判题

后续拓展的可能

  • 多语言支持

备注:本周的需求分析为上周的拓展需求

二、设计思路

在上周的基础上进行改进,首先增加了真分数。修改Operad类中的getOp1getOp2方法,使其随机生成整数或真分数。接下来是支持多运算符,新建了一个MakeQuestions类,具体代码在第三点进行解释。然后将原来的含有main函数的类分为两个类,一个是OutInputQuestionsToFile,另一个是OutputResultToFile。即一个用来生成题目并输出到文件,另一个用来文件读入并判题,再将结果输出到文件。

UML类图

三、实现过程中的关键代码

  • 支持真分数
package Arithmetic;

/**
 * Created by Administrator on 2017/5/15.
 */
//********************************************************************
//  RationalNumber.java       Java Foundations
//
//  Represents one rational number with a numerator and denominator.
//********************************************************************

public class RationalNumber
{
    private int numerator, denominator;

    //-----------------------------------------------------------------
    //  Constructor: Sets up the rational number by ensuring a nonzero
    //  denominator and making only the numerator signed.
    //-----------------------------------------------------------------
    public RationalNumber (int numer, int denom)
    {
        if (denom == 0)
            denom = 1;

        // Make the numerator "store" the sign
        if (denom < 0)
        {
            numer = numer * -1;
            denom = denom * -1;
        }

        numerator = numer;
        denominator = denom;

        reduce();
    }

    //-----------------------------------------------------------------
    //  Returns the numerator of this rational number.
    //-----------------------------------------------------------------
    public int getNumerator ()
    {
        return numerator;
    }

    //-----------------------------------------------------------------
    //  Returns the denominator of this rational number.
    //-----------------------------------------------------------------
    public int getDenominator ()
    {
        return denominator;
    }

    //-----------------------------------------------------------------
    //  Returns the reciprocal of this rational number.
    //-----------------------------------------------------------------
    public RationalNumber reciprocal ()
    {
        return new RationalNumber (denominator, numerator);
    }

    //-----------------------------------------------------------------
    //  Adds this rational number to the one passed as a parameter.
    //  A common denominator is found by multiplying the individual
    //  denominators.
    //-----------------------------------------------------------------
    public RationalNumber add (RationalNumber op2)
    {
        int commonDenominator = denominator * op2.getDenominator();
        int numerator1 = numerator * op2.getDenominator();
        int numerator2 = op2.getNumerator() * denominator;
        int sum = numerator1 + numerator2;

        return new RationalNumber (sum, commonDenominator);
    }

    //-----------------------------------------------------------------
    //  Subtracts the rational number passed as a parameter from this
    //  rational number.
    //-----------------------------------------------------------------
    public RationalNumber subtract (RationalNumber op2)
    {
        int commonDenominator = denominator * op2.getDenominator();
        int numerator1 = numerator * op2.getDenominator();
        int numerator2 = op2.getNumerator() * denominator;
        int difference = numerator1 - numerator2;

        return new RationalNumber (difference, commonDenominator);
    }

    //-----------------------------------------------------------------
    //  Multiplies this rational number by the one passed as a
    //  parameter.
    //-----------------------------------------------------------------
    public RationalNumber multiply (RationalNumber op2)
    {
        int numer = numerator * op2.getNumerator();
        int denom = denominator * op2.getDenominator();

        return new RationalNumber (numer, denom);
    }

    //-----------------------------------------------------------------
    //  Divides this rational number by the one passed as a parameter
    //  by multiplying by the reciprocal of the second rational.
    //-----------------------------------------------------------------
    public RationalNumber divide (RationalNumber op2)
    {
        return multiply (op2.reciprocal());
    }

    //-----------------------------------------------------------------
    //  Determines if this rational number is equal to the one passed
    //  as a parameter.  Assumes they are both reduced.
    //-----------------------------------------------------------------
    public boolean isLike (RationalNumber op2)
    {
        return ( numerator == op2.getNumerator() &&
                denominator == op2.getDenominator() );
    }

    //-----------------------------------------------------------------
    //  Returns this rational number as a string.
    //-----------------------------------------------------------------
    public String toString ()
    {
        String result;

        if (numerator == 0)
            result = "0";
        else
        if (denominator == 1)
            result = numerator + "";
        else
            result = numerator + "/" + denominator;

        return result;
    }

    //-----------------------------------------------------------------
    //  Reduces this rational number by dividing both the numerator
    //  and the denominator by their greatest common divisor.
    //-----------------------------------------------------------------
    private void reduce ()
    {
        if (numerator != 0)
        {
            int common = gcd (Math.abs(numerator), denominator);

            numerator = numerator / common;
            denominator = denominator / common;
        }
    }

    //-----------------------------------------------------------------
    //  Computes and returns the greatest common divisor of the two
    //  positive parameters. Uses Euclid's algorithm.
    //-----------------------------------------------------------------
    private int gcd (int num1, int num2)
    {
        while (num1 != num2)
            if (num1 > num2)
                num1 = num1 - num2;
            else
                num2 = num2 - num1;

        return num1;
    }
}

这是一个有理数的类,每一个对象都代表一个有理数。构造方法含有分子和分母两个参数,我只需限定分子小于分母,那么就可以得到一个真分数。在Operad类中,有两个私有方法

 private String  getA() {
        a = String.valueOf(rnd1.nextInt(10) + 1);
        return a;
    }
 private RationalNumber getB(){
        while (true) {
            c = rnd1.nextInt(10) + 1;
            d = rnd2.nextInt(10) + 1;
            b = new RationalNumber(c, d);
            if (c < d){
                break;
            }
        }

A是一个随机整数,我把它的范围设为1~10,B是一个随机真分数。而每一个操作数既可能为整数,也可能为真分数

public String getOp1(){
        if (rnd3.nextInt(2) == 0){
            op1 = getA();
        }
        else
            op1 = getB().toString();
        return op1;
    }
 public String getOp2(){
        if (rnd3.nextInt(2) == 0){
            op2 = getA();
        }
        else
            op2 = getB().toString();
        return op2;
    }

这样就实现了真分数。

  • 支持多运算符
 public String getExper(int i){
        expr = opd.getOp1() + getOperator();
        for (int j = 0; j < i-1; j++) {
            String s = opd.getOp1() + getOperator();
            expr += s;
        }
        expr = expr + opd.getOp2();
        return expr;
    }

我用循环的方法来获得多运算符。参数i代表运算符的个数。getOperator方法也是私有的,可以随机生成一个运算符(加、减、乘、除)。

  • 处理生成题目并输出到文件
package Arithmetic;

import java.io.*;

/**
 * Created by Administrator on 2017/5/19.
 */
public class IOFile {
    PrintStream  ps;


    public IOFile(String file){
        try {
            ps = new PrintStream(file);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }

    public void WriteQuestionsToFile(String s){
        ps.append(s);// 在已有的基础上添加字符串
    }
}

这个类只有一个方法,用来将题目写入文件。用到了PrinterStream类和它的append方法。在InputQuestionsToFile类中,只需调用该方法,并将表达式作为一个字符串传入参数即可。

  • 完成题目后从文件读入并判题
 public static void main(String[] args) throws IOException{
        Judgement jdg = new Judgement();
        NifixToSuffix nts = new NifixToSuffix();
        NumberFormat fmt = NumberFormat.getPercentInstance();
        FileInputStream fis = new FileInputStream("Exercises.txt");
        InputStreamReader isr = new InputStreamReader(fis);
        BufferedReader in = new BufferedReader(isr);
        StringTokenizer tokenizer1 = null, tokenizer2 = null;
        String token1, token2, token3, token4;
        String s1 = null;
        String str;
        int q = 0, count = 0;
        IOFile iof = new IOFile("ExercisesResult.txt");
        while ((str = in.readLine()) != null) {
            tokenizer1 = new StringTokenizer(str, ":");
            token1 = tokenizer1.nextToken();
            token2 = tokenizer1.nextToken();
            tokenizer2 = new StringTokenizer(token2, "=");
            token3 = tokenizer2.nextToken();
            token4 = tokenizer2.nextToken();
            nts.conversion(token3);
            if (token4.equals(jdg.evaluate(nts.getMessage()))) {
                s1 = "正确!";
                q++;
            } else {
                s1 = "错误,正确答案为:" + jdg.evaluate(nts.getMessage());
            }

            String s2 = str + "\n" + s1 + "\n\n";
            iof.WriteQuestionsToFile(s2);
            count++;
        }

            double accuracy = (double) q / count;
            String s3 = "完成" + count + "道题目,答对" + q + "道题,正确率为" + fmt.format(accuracy);
            iof.WriteQuestionsToFile(s3);

    }

以上是主要代码。因为文件中每个题目都是一行,且形式都为“题目1:表达式 =答案”。所以我用了BufferedReader中的readLine方法,可以一行一行的读取。读取一行之后,先以“:”为标记将字符串分开。把“题目n”赋值给token1,“表达式 =答案”赋值给token2,再将token2以“=”为标记,把表达式赋值给token3,答案赋值给token4。将token3转化为后缀表达式,进行计算,把计算结果与token4进行比较,再把整个结果写入到文件中。这样一直循环,直到文件中没有下一行为止。

  • 实现分数的运算

还是用RationalNumber类。其中定义好了加减乘除四种方法。在栈中进行计算时,每取出一个操作数,我都先将其转化为RationalNumber类型再计算。RationalNumber类中有一个toString方法,返回这个有理数的值,是String类型。具体转化方法如下

public RationalNumber tranIntoRationalNum (String s){
        String token1, token2;

        StringTokenizer tokenizer1 = new StringTokenizer(s, "/");
        token1 = tokenizer1.nextToken();
        if (tokenizer1.hasMoreTokens()) {
            token2 = tokenizer1.nextToken();
            r = new RationalNumber(Integer.parseInt(token1), Integer.parseInt(token2));
        }
        else {
            r = new RationalNumber(Integer.parseInt(token1),1);
        }
        return r;
    }

把每个操作数(String类型)以“/”为标记分开,第一个字符传入分子,第二个传入分母;如只有一个,则第一个传入分子,把“1”传入分母。从栈中取出操作数时,调用这个方法,就能将操作数转化为RationalNumber类型。再调用其中的加减乘除的方法进行计算。

private String evalSingleOp (char operation, RationalNumber op1, RationalNumber op2)
    {
        RationalNumber result = new RationalNumber(0,1);

        switch (operation)
        {
            case ADD:
                result = r1.add(r2);
                break;
            case SUBTRACT:
                result = r1.subtract(r2);
                break;
            case MULTIPLY:
                result = r1.multiply(r2);
                break;
            case DIVIDE:
                result = r1.divide(r2);
        }

        return result.toString();
    }

最终返回结果,为String类型。

四、测试方法

  • CalculatorTest

  • NifixToSuffixTest

  • RationalNumberTest

五、运行过程截图

  • Step One:运行OutputQuestionsToFile类,将生成的题目输出到Exercises.txt文件中

  • Step Two:运行OutputResultToFile类,将结果输出到ExercisesResult.txt文件中

六、代码托管地址

七、遇到的困难及解决方法

  • 问题1

在实现了生成含真分数的表达式后,计算出了问题。因为是转化为后缀表达式,通过栈的方法计算,之前的都是整数,只需将String类型用Integer.praseInt()方法转化成Int类型就可以了。可是加了真分数后,例如1/3,是一个字符串,在转化时就出错了。

  • 解决办法

原来的Judgement类中定义的栈中的元素是Int类型,我把它改成String类型,这样真分数就能进栈了,至于其它的Int类型的整型数,我修改了一下Operad类,将getOp1()getOp2()这两个方法的返回值都设置成String类型,如果生成的随机数为整型数,就用String.valueOf()方法将其转化为String类型。具体的代码在第三点实现过程中的关键代码有解释

  • 问题2

解决问题1后,随之而来的是计算问题。虽然将真分数压进了栈,但是之前定义的计算都是整型数的计算,真分数和真分数,真分数和整型数之间的计算并没有定义。

  • 解决办法

使用教材中的RationalNumber类。这个类的每一个对象都是一个有理数,里面定义了有理数的四则运算。我只需把每个操作数转化为RationalNumber类型,在调用其中的方法计算就好了,具体的解释参见第三点实现过程中的关键代码

八、对结对的小伙伴的评价

这周我设计代码,同时教我的搭档将我的设计实现,不过搭档写的是一些比较简单的代码。从上周什么也不会写,到现在能够根据我的设计思路写出一点东西,还是有进步的,最后加注释和修改类名也交给她做了。虽然有些进步,但还是有很多需要改进的地方。首先,最好再去巩固一下课本的知识,在讲解的过程中,发现有些书上的基本知识都不清楚,这样我讲起来有些费劲;其次,要有自己的思路,要学会自己设计程序并实现;最后,要多利用课余时间补上之前没学清楚的东西。希望在以后结对编程中能赶上步伐。

九、参考或引用的设计、实现

  • RationalNumber

这个类是教材上定义好的一个类,使用它生成真分数,并进行计算,得到的结果也可以用分数形式展现。

  • 处理生成题目并输出到文件

参考java的io操作(将字符串写入到txt文件中)

  • 完成题目后从文件读入并判题

参考从文本文件中读取数据(每一行为一个字符串数据)

十、PSP

PSP2.1 Personal Software Process Stages 预估耗时(小时) 实际耗时(小时)
Planning 计划
· Estimate · 估计这个任务需要多少时间 0.5 0.5
Development 开发
· Analysis · 需求分析 (包括学习新技术) 2 4
· Design Spec · 生成设计文档 1.5 1
· Design Review · 设计复审 (和同事审核设计文档) 1 1
· Coding Standard · 代码规范 (为目前的开发制定合适的规范) 0.5 1
· Design · 具体设计 2 3
· Coding · 具体编码 6 8
· Code Review · 代码复审 2 2.5
· Test · 测试(自我测试,修改代码,提交修改) 2 2.5
Reporting 报告
· Test Report · 测试报告 1 1.5
· Size Measurement · 计算工作量 0.5 0.5
· Postmortem & Process Improvement Plan · 事后总结, 并提出过程改进计划 0.5 0.5
合计 19.5 26
posted @ 2017-05-21 16:59  20162311张之睿  阅读(453)  评论(16编辑  收藏  举报