20162311 結對編程項目-四則運算 整體總結
一、需求分析
- 支持多運算符
- 支持真分數
- 用戶可選擇生成題目的等級
- 處理生成題目並輸出到文件
- 完成題目后從文件讀入並判題
后續拓展的可能
- 多語言支持
備注:本周的需求分析為上周的拓展需求
二、設計思路
在上周的基礎上進行改進,首先增加了真分數。修改Operad
類中的getOp1
和getOp2
方法,使其隨機生成整數或真分數。接下來是支持多運算符,新建了一個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
類型,在調用其中的方法計算就好了,具體的解釋參見第三點實現過程中的關鍵代碼
。
八、對結對的小伙伴的評價
-
結對搭檔:20162325金立清博客
-
評價
這周我設計代碼,同時教我的搭檔將我的設計實現,不過搭檔寫的是一些比較簡單的代碼。從上周什么也不會寫,到現在能夠根據我的設計思路寫出一點東西,還是有進步的,最后加注釋和修改類名也交給她做了。雖然有些進步,但還是有很多需要改進的地方。首先,最好再去鞏固一下課本的知識,在講解的過程中,發現有些書上的基本知識都不清楚,這樣我講起來有些費勁;其次,要有自己的思路,要學會自己設計程序並實現;最后,要多利用課余時間補上之前沒學清楚的東西。希望在以后結對編程中能趕上步伐。
九、參考或引用的設計、實現
RationalNumber
類
這個類是教材上定義好的一個類,使用它生成真分數,並進行計算,得到的結果也可以用分數形式展現。
- 處理生成題目並輸出到文件
- 完成題目后從文件讀入並判題
十、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 |