1. 程式人生 > >四則運算(Java)--溫銘淇,付夏陽

四則運算(Java)--溫銘淇,付夏陽

soft 改進 als mef 實現 rev nal 讀取 mat

GitHub項目地址

https://github.com/fxyJAVA/Calculation

四則運算項目要求:

程序處理用戶需求的模式為:

Myapp.exe -n num -r size

Myapp.exe -e

  • 基本功能列表:

    (1)【實現】使用 -n 參數控制生成題目的個數。

    (2)【實現】使用 -r 參數控制題目中數值(自然數、真分數和真分數分母)的範圍。

    (3)【實現】生成的題目中計算過程不能產生負數,也就是說算術表達式中如果存在形如e1 ? e2的子表達式,那麽e1 ≥ e2。

    (4)【實現】生成的題目中如果存在形如e1 ÷ e2的子表達式,那麽其結果應是真分數。

    (5)【實現】每道題目中出現的運算符個數不超過3個。

    (6)【實現】程序一次運行生成的題目不能重復,即任何兩道題目不能通過有限次交換+和×左右的算術表達式變換為同一道題目。生成的題目存入執行程序的當前目錄下的Exercises.txt文件

    (7)【實現】在生成題目的同時,計算出所有題目的答案,並存入執行程序的當前目錄下的Answers.txt文件。

    (8)【實現】程序應能支持一萬道題目的生成。

    (9)【實現】程序支持對給定的題目文件和答案文件,判定答案中的對錯並進行數量統計。

PSP表:

PSP2.1 Personal Software Process Stages 預估耗時(分鐘) 實際耗時(分鐘)
Planning 計劃 40 60
· Estimate · 估計這個任務需要多少時間 40 60
Development 開發 1280 1780
· Analysis · 需求分析 (包括學習新技術) 100 200
· Design Spec · 生成設計文檔 50 100
· Design Review · 設計復審 (和同事審核設計文檔) 40 100
· Coding Standard · 代碼規範 (為目前的開發制定合適的規範) 10 20
· Design · 具體設計 50 150
· Coding · 具體編碼 800 900
· Code Review · 代碼復審 30 50
· Test · 測試(自我測試,修改代碼,提交修改) 200 250
Reporting 報告 120 150
· Test Report · 測試報告 90 120
· Size Measurement · 計算工作量 5 10
· Postmortem & Process Improvement Plan · 事後總結, 並提出過程改進計劃 15 20
合計 1430 2090

  

解題思路:

初看題時,眼前的一道難關就是分數的問題,真分數,假分數,分數計算等。剛開始我和搭檔溫,對這一步的實現,首先我和溫是分開實現的,我是單純用字符串,字符串切分進行結果的計算,十分繁雜,且不利於後續的功能實現:加括號,順序隨機化等。後來發現溫將數字寫成一個類,十分驚喜於這個巧妙的設計,java本身就是面向對象的語言,我卻忽略了這個重要的點,純用面向過程思維來考慮了。取長補短走過了這一道難關,將數字定義為一個類極大便利了我們後續功能的實現。後續遇到的判重問題,我想的是用HashMap的特性,一個key對應一個value,key是唯一key。如果我將算式的結果作為key,那麽value就是算式本身。我們可知,結果不同的算式一定不相同,因此僅這一步我們就過濾掉了重復的問題,但因為此方法有一定的誤判可能,於是在後續補上了判重的方法,來降低誤判的可能。

設計實現過程

以main()為中心,通過cmd傳入參數到main(String[] args)獲取操作和文件路徑,根據參數來判斷進行何種操作 。生成算式則調用Utils的靜態方法生成,要寫文件和檢查則調用FileUtils的靜態方法生成,而Fraction類則貫穿其中,是功能實現的核心。

技術分享圖片

效能分析

因為測試的時候未發現生成算式和寫文件效率並未出很低的情況,在生成算式和寫文件時,以10000條數據為例,測試僅用了383毫秒,最壞情況也不超過2秒,暫無思路做到進一步的方法優化。
技術分享圖片

代碼說明

Main.java,通過args獲取要執行的操作:

根據程序處理用戶需求的模式

1)Myapp.exe -n num -r size

生成題目,其中num為題數,size為生成數的大小

調用FileUtils.creatFile(Utils.createSuanShi(num,size))

2)Myapp.exe -e exercisefile.txt -a answerfile.txt

判斷答案的正確率,其中exercisefile.txt為生成練習題的路徑 、answerfile.txt為答案的路徑

調用FileUtils.check(exercisePath,answerPath);

import java.io.IOException;

public class main {

    public static void main(String[] args) {
        int num = 0;
        int size = 0;
        String exercisePath = null;
        String answerPath = null;
        if(args.length != 0){
            for(int i = 0; i < args.length; i++){
                //"-n指令"
                if(args[i].equals("-n")) {
                    try {
                        num = Integer.parseInt(args[i+1]);
                    }catch (NumberFormatException e) {
                        System.out.println("參數有誤!");
                    }catch (ArrayIndexOutOfBoundsException e){
                        System.out.println("參數有誤!");
                    }
                }
                //"-r指令"
                if(args[i].equals("-r")) {
                    try {
                        size = Integer.parseInt(args[i+1]);
                    }catch (NumberFormatException e) {
                        System.out.println("參數有誤!");
                    }catch (ArrayIndexOutOfBoundsException e){
                        System.out.println("參數有誤!");
                    }
                }
                if(args[i].equals("-e")){
                    try{
                        exercisePath = args[i+1];
                    }catch (ArrayIndexOutOfBoundsException e){
                        System.out.println("參數有誤!");
                    }
                }
                if(args[i].equals("-a")){
                    try{
                        answerPath = args[i+1];
                    }catch (ArrayIndexOutOfBoundsException e){
                        System.out.println("參數有誤!");
                    }
                }
            }
            if(num != 0 && size != 0){
                FileUtils.creatFile(Utils.createSuanShi(num,size));
            }
            if(exercisePath != null && answerPath != null){
                try {
                    FileUtils.check(exercisePath,answerPath);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

以下是相應的util類:
首先是創造算式集合方法 createSuanShi,在這裏傳入決定生成的算式數目,算式取值範圍;
createFun則是生成算式方法,根據傳過來的要求數目,while循環生成算式,放入HashMap。
這裏算式結果作為key,算式本身作為value。這樣做的目的是判重,當我生成的結果在hashmap中存在value,我就判定該算式不成立,算入重復,但這樣做會有錯誤過濾。因而在該方法中又重新定義了判重的方法,思路為:先判斷結果是否相同->再判斷運算符數目是否相同且相等->最後判斷運算數是否相等,相等則我認為是重復算式,不計入map中。
createFra為生成算數,同時判斷是否要生成分數
whoBig為判斷算數a,b的大小,以防出現結果為負數
最後簡要說說加入()的時機,因為傳參是數組的緣故,所以根據計算符號的先後位置來決定是否加入括號,例如:我傳入了運算符順序是:+,—,* 在運算到乘法的時候,因為我已經計算了+,-的結果,所以我在這裏需要判斷前兩位操作符是什麽,若為+,—,則代表著我需要加入乘法前,給已生成的算式加上()。其他情況皆以此類推。

import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashMap;

public class Utils {
    public static HashMap<String, String> createSuanShi(int createNum, int size) {
        if (size == 0) {
            size = 100;
        }
        HashMap<String, String> weWant = new LinkedHashMap<>();
        while (weWant.size() < createNum) {
            int opNum = (int) (Math.random() * 3) + 1;
            String[] op = new String[opNum];
            for (int j = 0; j < opNum; j++) {
                int temp = (int) (Math.random() * 4);
                switch (temp) {
                    case 0: {
                        op[j] = "+";
                        break;
                    }
                    case 1: {
                        op[j] = "-";
                        break;
                    }
                    case 2: {
                        op[j] = "*";
                        break;
                    }
                    case 3: {
                        op[j] = "÷";
                        break;
                    }
                }
            }
            String[] result = createFun(op, size).split("=");

            //判重
            if ((weWant.get(result[1]))!=null) {
                String whoAreYou = weWant.get(result[1]);
                int calChar =0;
                for (int a=0;a<op.length;a++) {
                    if (whoAreYou.contains(op[a]))
                        calChar++;
                }
                if(calChar == op.length) {
                    StringBuilder sb = new StringBuilder(whoAreYou);
                    while(sb.toString().contains("(")) {
                        sb.deleteCharAt(sb.lastIndexOf("("));
                    }
                    while(sb.toString().contains(")")) {
                        sb.deleteCharAt(sb.lastIndexOf(")"));
                    }

                    StringBuilder sb2 = new StringBuilder(result[0]);
                    while(sb2.toString().contains("(")) {
                        sb2.deleteCharAt(sb2.lastIndexOf("("));
                    }
                    while(sb2.toString().contains(")")) {
                        sb2.deleteCharAt(sb2.lastIndexOf(")"));
                    }

                    String[] numArr1 = sb.toString().split("[\\+\\-\\*\\÷]");
                    String numArr2 = Arrays.toString(sb2.toString().split("[\\+\\-\\*\\÷]"));
                    int xxx=0;
                    for (int b = 0;b<numArr1.length;b++) {
                        if(numArr2.contains(numArr1[b])) {
                            xxx++;
                        }
                    }
                    if(xxx==numArr1.length) {
                        continue;
                    }
                }
            }
            weWant.put(result[1], result[0]);
        }
        return weWant;
    }

    public static String createFun(String[] op, int size) {
        StringBuffer suanShi = new StringBuffer();
        //確定符號數
        int length = op.length;
        //初始化結果為0
        Fraction result = new Fraction(0, 1);
        for (String c : op) {
            switch (c) {
                case "+": {
                    if (suanShi.length() > 0 && suanShi != null) {
                        Fraction c1 = createFra(size);
                        result = result.add(c1);
                        if ((suanShi.toString().contains("*") || suanShi.toString().contains("÷")) && (int) (Math.random() * 2) == 0 && length == 2) {
                            suanShi.insert(0, c1.getFraction() + "+");
                        } else if (suanShi.toString().contains("*") && suanShi.toString().contains("÷") && (int) Math.random() * 3 == 0) {
                            suanShi.insert(0, c1.getFraction() + "+");
                        } else {
                            if (length == 3 && suanShi.toString().contains("÷") && op[0] == "-") {
                                suanShi.insert(0, "(");
                                suanShi.insert(suanShi.length(), ")");
                            }
                            suanShi.append("+" + c1.getFraction());
                        }
                    } else {
                        Fraction a = createFra(size);
                        Fraction b = createFra(size);
                        result = a.add(b);
                        suanShi.append(a.getFraction() + "+" + b.getFraction());
                    }
                    break;
                }
                case "-": {
                    if (suanShi.length() > 0 && suanShi != null) {
                        Fraction c1 = createFra(size);
                        boolean flag = whoBig(result, c1);
                        if (flag) {
                            result = c1.sub(result);
                        } else {
                            result = result.sub(c1);
                        }

                        if ((flag && suanShi.toString().contains("*") || suanShi.toString().contains("÷")) && (int) (Math.random() * 3) == 0 && (length == 2 || length == 3)) {
                            if (length == 3 && (suanShi.toString().contains("*") || suanShi.toString().contains("÷")) && op[1] == "+") {
                                suanShi.insert(0, "(");
                                suanShi.insert(suanShi.length(), ")");
                            }
                            suanShi.insert(0, c1.getFraction() + "-");
                        } else if (flag && suanShi.toString().contains("*") && suanShi.toString().contains("÷") && (int) (Math.random() * 3) == 0) {
                            if (length == 3 && (suanShi.toString().contains("*") || suanShi.toString().contains("÷")) && op[1].equals("+")) {
                                suanShi.insert(0, "(");
                                suanShi.insert(suanShi.length(), ")");
                            }
                            suanShi.insert(0, c1.getFraction() + "-");
                        } else {
                            if (flag) {
                                suanShi.insert(0, "(");
                                suanShi.insert(suanShi.length(), ")");
                                suanShi.insert(0, c1.getFraction() + "-");
                            } else {
                                suanShi.append("-" + c1.getFraction());
                            }
                        }
                        if (length == 3 && op[1].equals("-") && (op[2].equals("*") || op[2].equals("÷"))) {
                            suanShi.insert(0, "(");
                            suanShi.insert(suanShi.length(), ")");
                        }
                    } else {
                        Fraction a = createFra(size);
                        Fraction b = createFra(size);
                        if (whoBig(a, b)) {
                            result = b.sub(a);
                            suanShi.append(b.getFraction() + "-" + a.getFraction());
                        } else {
                            result = a.sub(b);
                            suanShi.append(a.getFraction() + "-" + b.getFraction());
                        }
                    }
                    break;
                }
                case "*": {
                    if (suanShi.length() > 0 && suanShi != null) {
                        Fraction c1 = createFra(size);
                        result = result.mul(c1);
                        if (length == 3 && (op[0].equals("+") || op[0].equals("-")) && (op[1].equals("+") || op[1].equals("-")) && !suanShi.toString().contains("(")) {
                            suanShi.insert(0, "(");
                            suanShi.insert(suanShi.length(), ")");
                        } else {
                            if ((length == 2 || length == 3) && ((suanShi.toString().contains("+") || suanShi.toString().contains("-"))) && !suanShi.toString().contains("(")) {
                                suanShi.insert(0, "(");
                                suanShi.insert(suanShi.length(), ")");
                            }
                            if (length == 3 && (suanShi.toString().contains("÷") && (suanShi.toString().contains("+") || suanShi.toString().contains("-"))) && !suanShi.toString().contains("((") && op[0] != "*") {
                                suanShi.insert(0, "(");
                                suanShi.insert(suanShi.length(), ")");
                            }
                        }
                        suanShi.append("*" + c1.getFraction());
                    } else {
                        Fraction a = createFra(size);
                        Fraction b = createFra(size);
                        result = a.mul(b);
                        suanShi.append(a.getFraction() + "*" + b.getFraction());
                    }
                    break;
                }
                case "÷": {
                    if (suanShi.length() > 0 && suanShi != null) {
                        Fraction c1 = createFra(size);
                        while (c1.getNumerator() == 0) {
                            c1 = createFra(size);
                        }
                        result = result.div(c1);
                        if ((length == 3 && (op[0].equals("+") || op[0].equals("-")) && (op[1].equals("+") || op[1].equals("-")) && !suanShi.toString().contains("("))) {
                            suanShi.insert(0, "(");
                            suanShi.insert(suanShi.length(), ")");
                        }

                        if ((length == 2 || length == 3) && (suanShi.toString().contains("+") || suanShi.toString().contains("-")) && (suanShi.toString().contains("*") && suanShi.toString().contains("÷"))) {
                            suanShi.insert(0, "(");
                            suanShi.insert(suanShi.length(), ")");
                        }
                        if (length == 3 && (suanShi.toString().contains("*")) && ((suanShi.toString().contains("+") || suanShi.toString().contains("-"))) && !suanShi.toString().contains("((") && !op[0].equals("*")) {
                            suanShi.insert(0, "(");
                            suanShi.insert(suanShi.length(), ")");
                        }
                        if (length == 3 && (suanShi.toString().contains("+") || suanShi.toString().contains("-")) && (op[2].equals("-") || op[2].equals("+"))) {
                            suanShi.insert(0, "(");
                            suanShi.insert(suanShi.length(), ")");
                        }
                        if (length == 3 && op[1].equals("÷") && (op[0].equals("+") || op[0].equals("-"))) {
                            suanShi.insert(0, "(");
                            suanShi.insert(suanShi.length(), ")");
                        }
                        suanShi.append("÷" + c1.getFraction());
                    } else {
                        Fraction a = createFra(size);
                        Fraction b = createFra(size);
                        while (b.getNumerator() == 0) {
                            b = createFra(size);
                        }
                        result = a.div(b);
                        suanShi.append(a.getFraction() + "÷" + b.getFraction());
                    }
                    break;
                }
            }
        }
        return suanShi.append("=" + (result.getNumerator() == 0 ? 0 : result.getFraction())).toString();
    }

    private static boolean whoBig(Fraction l, Fraction r) {

        boolean flag = false;
        if (l.getNumerator() * r.getDenominator() < l.getDenominator() * r.getNumerator()) {
            flag = true;
        }
        return flag;
    }

    private static Fraction createFra(int size) {
        if ((int) (Math.random() * 3) == 0) {
            return new Fraction((int) (Math.random() * size + 1), (int) (Math.random() * size) + 2);
        } else {
            return new Fraction((int) (Math.random() * size + 1), 1);
        }
    }
}

數字類,得意於將數字定義為一個類,且集合了加減乘除,約分等對分數的處理,使得後面的流程可以專註於業務需求上,計算方面只需丟給該類的方法即可完成運算。

public class Fraction {
    private int numerator;//分子
    private int denominator;//分母

    public Fraction(int a,int b){
        setNumeratorAndDenominator(a,b);
    }
    public void setNumeratorAndDenominator(int a, int b){  // 設置分子和分母
        int c = 1;
        if(a!=0) {
            c = largestCommonDivisor(a, b);// 計算最大公約數
        }
        numerator = a / c;
        denominator = b / c;
    }
    public int largestCommonDivisor(int a,int b){  // 求a和b的最大公約數
        if(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;
    }
    public int getNumerator(){
        return numerator;
    }
    public int getDenominator(){
        return denominator;
    }
    public String getFraction(){  //獲得分數的表達,根據分子分母的大小關系是否化為帶分數
        String str;
        if(denominator == 1) {  //分母為1,只看分子的大小
            str = String.valueOf(this.numerator);
        }else if(numerator == denominator){  //分子分母相等,輸出1
            str = "1";
        }else if(numerator > denominator){  //分子大於分母,轉化為帶分數
            int roundNumber = numerator / denominator;
            int newNumerator = numerator - denominator * roundNumber;
            str = roundNumber + "‘" + newNumerator + "/" + this.denominator;
        }else{  //否則按正常的分數輸出
            str = this.numerator + "/" + this.denominator;
        }
        return str;
    }

    public Fraction add(Fraction r){  // 加法運算,通分後再運算
        int a = r.getNumerator();
        int b = r.getDenominator();
        int newNumerator = this.numerator * b + this.denominator * a;
        int newDenominator = this.denominator * b;
        Fraction result = new Fraction(newNumerator,newDenominator);
        return result;
    }

    public Fraction sub(Fraction r){  // 減法運算
        int a = r.getNumerator();
        int b = r.getDenominator();
        int newNumerator = numerator * b - denominator * a;
        int newDenominator = denominator * b;
        Fraction result = new Fraction(newNumerator,newDenominator);
        return result;
    }

    public Fraction mul(Fraction r){ // 乘法運算
        int a = r.getNumerator();
        int b = r.getDenominator();
        int newNumerator = this.numerator * a;
        int newDenominator = this.denominator * b;
        Fraction result = new Fraction(newNumerator,newDenominator);
        return result;
    }

    public Fraction div(Fraction r){  // 除法運算
        int a = r.getNumerator();
        int b = r.getDenominator();
        int newNumerator = numerator * b;
        int newDenominator = denominator * a;
        Fraction result = new Fraction(newNumerator,newDenominator);
        return result;
    }
}

FileUtils類主要用於生成文件,和讀取文件
createFile為生成文件方法,前面通過utils方法獲得算式集,叠代獲取算式和結果值,通過字符流(PrintWriter)分別寫入到兩個文件:Exercises@日期.txt Answers@日期
check方法主要用於比對答案是否正確,通過獲取傳進來的question獲取對應的答案文件讀取答案與讀取到的yourAnswer文件匹配,匹配結果寫入到grade文件中,以及輸出到控股臺。

import java.io.*;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;

public class FileUtils {
    public static void creatFile(HashMap map) {
        LocalDateTime time = LocalDateTime.now();
        String timeFor = time.format(DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss"));
        File question = new File("D:\\Exercises@"+timeFor+".txt");
        File answer = new File("D:\\Answers@"+timeFor +".txt");
        try {
            question.createNewFile();
            answer.createNewFile();
        } catch (IOException e) {
            e.printStackTrace();
        }

        try (PrintWriter questionWrite = new PrintWriter(new FileWriter(question, true));
             PrintWriter answerWrite = new PrintWriter(new FileWriter(answer, true));) {
            int jiShu = 0;
            for (Object entry : map.entrySet()) {
                Map.Entry<String, String> temp = (Map.Entry<String, String>) entry;
                String key = temp.getKey();
                String value = temp.getValue();
                questionWrite.write(jiShu+1+".    "+value+"= ");
                questionWrite.write("\r\n");
                questionWrite.write("\r\n");
                answerWrite.write(jiShu+1+".    "+key);
                answerWrite.write("\r\n");
                answerWrite.write("\r\n");
                jiShu++;
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void check(String question,String yourAnswer) throws IOException {
        String answers = question.split("@")[1];
        File answersOff =  new File("D:\\Answers@"+answers);
        File yourAnswers = new File(yourAnswer);
        if(!yourAnswers.exists()) {
            System.out.println("未找到文件,請重新確認文件路徑");
            System.exit(0);
        }
        if(!answersOff.exists()) {
            System.out.println("答案文件不存在,請確認");
            System.exit(0);
        }
        LocalDateTime time = LocalDateTime.now();
        String timeFor = time.format(DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss"));
        File grade = new File("D:\\Grade@"+timeFor+".txt");
        grade.createNewFile();
        try(BufferedReader answersOffReader = new BufferedReader(new FileReader(answersOff));
            BufferedReader yourAnswersReader = new BufferedReader(new FileReader(yourAnswers));
            PrintWriter gradeWrite = new PrintWriter(new FileWriter(grade))) {
            String temp ;
            String ans ;
            StringBuilder r = new StringBuilder();
            r.append("(");
            StringBuilder w = new StringBuilder();
            w.append("(");
            int right = 0;
            int wrong = 0;
            ans=answersOffReader.readLine();
            while ((temp=yourAnswersReader.readLine())!=null ) {
                if(temp.equals("\r\n") || temp.equals("")) {
                    continue;
                }
                while(ans==null || ans.equals("")) {
                    ans=answersOffReader.readLine();
                }
                String xuHao =ans.split("\\.")[0];
                if(temp.split("\\.").length>1) {

                    ans =ans.split("\\.")[1].trim();
                    String tempAnswer = temp.split("\\.")[1].trim();
                    if(tempAnswer.equals(ans)) {
                        r.append(xuHao+",");
                        right++;
                    }else {
                        w.append(xuHao+",");
                        wrong++;
                    }
                }else {
                    w.append(xuHao+",");
                    wrong++;
                    continue;
                }
                ans = answersOffReader.readLine();
            }
            if(r.toString().contains(",")) {
                r.deleteCharAt(r.lastIndexOf(","));
            }
            if(w.toString().contains(",")) {
                w.deleteCharAt(w.lastIndexOf(","));
            }
            r.insert(r.length(),")");
            w.insert(w.length(),")");
            gradeWrite.write("Correct:"+right+r.toString());
            gradeWrite.write("\r\n");
            gradeWrite.write("Wrong:"+wrong+w.toString());
            System.out.println("Correct:"+right+r.toString());
            System.out.println("Wrong:"+wrong+w.toString());
        }
    }

}

測試運行

答題者的答案需另起一個文檔,格式參照正確答案
命令行:
技術分享圖片
題目:
技術分享圖片
答案:
技術分享圖片
判斷答案:
技術分享圖片
生成文件:
技術分享圖片
覆蓋率:
技術分享圖片

項目小結

通過這次項目實戰,我們又復習了javaSE的一些知識,比如java的集合類HashMap,文件的輸入輸出流,面向對象等知識,還有些細節性的一些知識點,比如為什麽用StringBuilder而不用STring和StringBuffer等。又因為是兩人合作開發,在開發時就不能
一意孤行,要結合隊友的思路,彼此交流,言語表達要使對方能理解你的思路,探討不足和取長補短,這樣才能進行下一步的深入研究,也才有了我們現在完成的項目。通過此次開發,我們都體會到了合作開發的優勢,開始有了團隊意識,寫代碼時,會留
意註釋是否到位等,另外一項收獲就是如何進行項目管理,版本控制,防止開發時出現版本沖突等問題,較好的進行版本叠代。

四則運算(Java)--溫銘淇,付夏陽