軟工作業-四則運算生成器
git地址:https://www.cnblogs.com/Nigghod/p/9714359.html
組員:洪崇偉、林浩
一、PSP表-預估耗時
PSP2.1 | Personal Software Process Stages | 預估耗時(分鐘) | 實際耗時(分鐘) |
---|---|---|---|
Planning | 計劃 | 60 | 30 |
· Estimate | · 估計這個任務需要多少時間 | 60 | 30 |
Development | 開發 | 470 | 505 |
· Analysis | · 需求分析 (包括學習新技術) | 30 | 20 |
· Design Spec | · 生成設計文檔 | 10 | 15 |
· Design Review | · 設計復審 (和同事審核設計文檔) | 10 | 15 |
· Coding Standard | · 代碼規範 (為目前的開發制定合適的規範) | 20 | 10 |
· Design | · 具體設計 | 30 | 25 |
· Coding | · 具體編碼 | 300 | 320 |
· Code Review | · 代碼復審 | 10 | 20 |
· Test | · 測試(自我測試,修改代碼,提交修改) | 60 | 80 |
Reporting | 報告 | 30 | 40 |
· Test Report | · 測試報告 | 10 | 10 |
· Size Measurement | · 計算工作量 | 10 | 10 |
· Postmortem & Process Improvement Plan | · 事後總結, 並提出過程改進計劃 | 10 | 20 |
合計 | 560 | 575 |
二、解題思路
- 首先這個題目有分數的要求,所以在一開始就應該考慮到分數的實現,因為java不存在可以處理分數運算的類,這個應該在一開始就考慮清楚,我們的處理方式是將整數與分數作為一個對象來處理,即整數就是分母為1的分數。
- 計算便可以直接用分數的運算了。
- 因為題目要求命令行,所以我們找了腳本的方法來實現命令行操作
三、設計實現過程
- 有三個包,分別為service(服務包),model(實體包),main(主程序)
- service裏有四個類,分別包括了計算操作,分數實體類處理操作,檢查操作與生成式子操作
- model裏有三個類,計算等式的計算類,分數類,結果集合
- main裏主要是以上兩個包中的方法對應整合
四、代碼說明
整個程序難點就在於分數的運算,整數,小數運算可以用自帶的操作運算符,但是在java裏面卻沒有一個基本類型去表示分數。很多人可能會考慮到把分數化成小數再運算再轉為分數,其實不然,有很多情況下是無法進行轉換的,因為java語言中的double類型所表示的精度也是有限的。於是結合java面向對象的思想特征,應該先定義要給分數類,並封裝相關的運算方法。代碼如下
分數類:
/*
* 構建一個分數類,用來表示分數,封裝相關的方法
*/
public class Fraction {
private int denominator;// 分母
private int nominator;// 分子
// 構建一個分數
public Fraction(int denominator, int nominator) {
super();
this.denominator = denominator;
this.nominator = nominator;
}
public Fraction(int nominator) {
this.denominator = 1;
this.nominator = nominator;
}
public Fraction() {
super();
}
// 判斷構建的是一個分數還是一個整數,不超過limit的數值
public Fraction(boolean l, int limit) {
Random r = new Random();
// 這是一個分數
if (l == true) {
int index = r.nextInt(limit);
int index2 = r.nextInt(limit);
while(index==0) {
index = r.nextInt(limit);
// System.out.println("會生成0:"+index);
}
// System.out.println("不會生成0:"+index);
this.denominator = index;
this.nominator = index2;
// 這是一個整數
} else {
int index = r.nextInt(limit);
this.denominator = 1;
this.nominator = index;
}
}
public int getDenominator() {
return denominator;
}
public void setDenominator(int denominator) {
this.denominator = denominator;
}
public int getNominator() {
return nominator;
}
public void setNominator(int nominator) {
this.nominator = nominator;
}
// 加法運算
public Fraction add(Fraction r) {
int a = r.getNominator();// 獲得分子
int b = r.getDenominator();// 獲得分母
int newNominator = nominator * b + denominator * a;
int newDenominator = denominator * b;
Fraction result = new Fraction(newDenominator, newNominator);
return result;
}
// 減法運算
public Fraction sub(Fraction r) {
int a = r.getNominator();// 獲得分子
int b = r.getDenominator();// 獲得分母
int newNominator = nominator * b - denominator * a;
int newDenominator = denominator * b;
Fraction result = new Fraction(newDenominator, newNominator);
return result;
}
// 分數的乘法運算
public Fraction muti(Fraction r) { // 乘法運算
int a = r.getNominator();// 獲得分子
int b = r.getDenominator();// 獲得分母
int newNominator = nominator * a;
int newDenominator = denominator * b;
Fraction result = new Fraction(newDenominator, newNominator);
return result;
}
// 分數除法運算
public Fraction div(Fraction r) {
int a = r.getNominator();// 獲得分子
int b = r.getDenominator();// 獲得分母
int newNominator = nominator * b;
int newDenominator = denominator * a;
Fraction result = new Fraction(newDenominator, newNominator);
return result;
}
// 用輾轉相除法求最大公約數
private static long gcd(long a, long b) {
return b == 0 ? a : gcd(b, a % b);
}
// 對分數進行約分
public void Appointment() {
if (nominator == 0 || denominator == 1)
return;
// 如果分子是0或分母是1就不用約分了
long gcd = gcd(nominator, denominator);
this.nominator /= gcd;
this.denominator /= gcd;
}
public int existZero(){
if(this.nominator<0||this.denominator<0){
return 0;
}else {
return 1;
}
}
}
在這個分數類中,我們定義了分子和分母,然而整數其實一個分數,只不過這個分數的分母為1.
至於運算也只是根據運算法則對分數的分子分母進行運算,保證了
在程序中,運算符的出現數量以及類型是隨機的,用數組進行存儲,用一個隨機數Ramdon
char[] c = { ‘+‘, ‘-‘, ‘*‘, ‘÷‘ }//顯示四種基本運算符
r.nextInt(4);//隨機生成四種基本運算符的一種
int s = r.nextInt(3);// 生成運算符的數量
用這種隨機數的形式保證了式子的隨機性。隨機出現,隨機生成,之後只要根據多項式的運算,對式子進行運算即可。之後將生成的式子放在一個list裏面,包括運算符以及分數類
運算
四則運算具有優先級,先算乘除再算加減,如果采用if表達的形式,對優先級進行判定,那麽三個運算符就有64種情況,很明顯是不可采取的,容易使代碼失去可讀性,在這種情況下,我們采取遞歸來解決這種問題。
//分式的計算方法
public Fraction calculate(List l){
int muldiv = MulDivExist(l);
if(muldiv != -1){
String s = MulDiv(l,muldiv);
if(s.equals("error")){
return null;
}
}else {
String s = AddSub(l);
if(s.equals("error")){
return null;
}
}
if (l.size() == 1) {
return (Fraction) l.get(0);
}
return calculate(l);
}
/*
* 判斷分式裏面是否有乘除
* 有乘除返回乘除的位置,沒乘除返回-1
*/
public int MulDivExist(List list){
for (int i = 0; i < list.size(); i++) {
if (list.get(i).equals("*") || list.get(i).equals("/")) {
return i;
}
}
return -1;
}
//計算分式的乘除,計算結果往前放
public String MulDiv(List l,int muldiv){
String fuhao = (String) l.remove(muldiv);
Fraction first = (Fraction) l.get(muldiv-1);
Fraction last = (Fraction) l.get(muldiv);
l.remove(muldiv);
if (fuhao.equals("*")) {
Fraction result = first.muti(last);
l.set(muldiv - 1,result);
if(result.existZero()==0){
return "error";
}
}
if (fuhao.equals("/")) {
Fraction result = first.div(last);
l.set(muldiv - 1,result);
if(result.existZero()==0){
return "error";
}
}
return "right";
}
//計算分式的加減,計算結果往前放
public String AddSub(List list){
for (int i = 0; i < list.size(); i++) {
if (list.get(i).equals("+")) {
Fraction first = (Fraction) list.get(i-1);
list.remove(i);
Fraction last = (Fraction) list.get(i);
list.remove(i);
Fraction result = first.add(last);
list.set(i - 1, result);
i--;
if(result.existZero()==0){
return "error";
}
}
if (list.get(i).equals("-")) {
Fraction first = (Fraction) list.get(i-1);
list.remove(i);
Fraction last = (Fraction) list.get(i);
list.remove(i);
Fraction result = first.sub(last);
list.set(i - 1, result);
i--;
if(result.existZero()==0){
return "error";
}
}
}
return "right";
}
在遞歸計算之前,先做一個判定,對生成的式子進行判別,如果有乘除,那就優先采用乘除的遞歸,然後再用遞歸計算加減。這樣就保證了運算的優先級問題。
去重
式子需要去重,保證每次生成的都是不一樣的式子。去重采用了一種字符串比較的方法,在此之前,重寫了分數類的toString方法,只要兩個字符串組成字符完全相同即:組成兩個式子的字符完全一樣就可以說明這兩個式子是重復的。
重寫的toString方法
@Override
public String toString() {
Appointment();
if(this.denominator == 0){
System.out.println(this.nominator + "|" + this.denominator);
System.out.println("分母為0");
}
if (this.denominator == 1 || this.nominator == 0) {
return "" + this.nominator;
}else if (this.nominator > this.denominator) {
if(nominator % denominator==0){
return "" + nominator / denominator;
}
return "" + nominator / denominator + "," + nominator % denominator + "/" + denominator;
}else{
return "" + this.nominator + "/" + this.denominator;
}
}
去重代碼:
//查重,若有重復那就返回ture
public boolean isRepeat(List<List<String>> list, List<String> set) {
if (list.size() <= 0) {
return false;
}
Iterator<String> iterator = set.iterator();
for (List l_set : list) {
if (l_set == null || l_set.size() != set.size() || l_set.size() <= 0 || set.size() <= 0) {
continue;
}
int i = 0;
while(iterator.hasNext()){
if(l_set.contains(iterator.next())){
i = i+1;
}
}
if(i == set.size()){
return true;
}
}
return false;
}
通過這段去重代碼可以將重復的式子篩選出去,保證了整個文件中式子的獨立性。
計算結果的校驗
一個式子,等式右邊的就是結果,一個式子就是一個字符串,只要用字符串處理函數將等式右邊的結果截取出來,與用戶的輸入進行對比就能得出結果與否。
相關代碼如下:
while((str1=reader1.readLine())!=null&&(str2=reader2.readLine())!=null){
if(!str1.trim().equals(str2.trim())){
String[] str = str1.split("\\.");
error = error + str[0]+ ",";
errornum ++ ;
}else {
String[] str = str1.split("\\.");
correct = correct + str[0] + ",";
correctnum ++;
}
}
if(error.equals("")){
error = "Wrong: " + errornum + "";
}else {
error = "Wrong: " + errornum + "(" + error.substring(0,error.length()-1) + ")";
}
if(correct.equals("")){
correct = "Correct: " + correctnum + "";
}else {
correct = "Correct: " + correctnum + "("+correct.substring(0, correct.length()-1)+")";
}
m.put("wrong", error);
m.put("correct", correct);
return m;
}
將結果放在一個map中,便於讀取,得出結果。
五、測試運行
首先要將jar文件與bat文件放置在同一目錄下
進入命令行界面
若是未輸入參數,則
正確輸入生成題目的參數命令
正確輸入測試文件與答案文件
若是全對
出現錯誤
六、總結
問題
- 一開始並沒有思考清楚題目中的分數問題,想著解決了分數就行,結果最後推倒重來。
- 在程序開發過程中,雖然使用了git管理,但因為不熟悉發生了一些錯誤,修改的代碼上傳後下拉發生了沖突,以後在團隊編程中,要註意代碼改動情況,和隊員對接好。
結對好處
- 可以互相發現對方的編碼問題,及時做出調整
- 在討論中,不斷地想出新的點子來實現功能,很好的促進項目的開發
軟工作業-四則運算生成器