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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM