基於Java實現的四則運算程序


一 . 項目介紹

項目成員:黃梓塏  蔡海傑

項目倉庫:github

二. PSP表格

PSP2.1 Personal Software Process Stages 預估耗時(分鍾) 實際耗時(分鍾)
Planning 計划 20   40
· Estimate 估計這個任務需要多少時間 10 10
Development 開發 20 30     
· Analysis 需求分析 (包括學習新技術) 30 30
· Design Spec · 生成設計文檔 20 40
· Design Review · 設計復審 (和同事審核設計文檔) 10 10
· Coding Standard · 代碼規范 (為目前的開發制定合適的規范) 20 20
· Design · 具體設計 20 40
· Coding · 具體編碼 400 600
· Code Review · 代碼復審 10 30
· Test · 測試(自我測試,修改代碼,提交修改) 30 30
Reporting 報告 30 40
· Test Report · 測試報告 20 30
· Size Measurement · 計算工作量 20 20
· Postmortem & Process Improvement Plan · 事后總結, 並提出過程改進計划 20 20
合計   680 990

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

三. 項目功能

  由界面輸入參數,實現了題目的生成以及去重,問題與答案的文件保存,用戶輸入答案文件與標准答案的校對以及結果文件生成

  運行示例(Answerfile.txt為用戶提交文件):

  

四. 項目分析與代碼設計

  1. 由於題目設計真分數運算,所以采用分數作為基本的運算單位。使用ExpressionResult類保存每條表達式以及它的運算結果

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

    //取最大公因數化簡
    public Fraction(Integer n, Integer d) {
        Integer GCD = Calculator.GCD(n, d);
        this.numerator = n /= GCD;
        this.denominator = d /= GCD;
    }


    //重寫toString方法,以真分數形式表示
    @Override
    public String toString() {
        if (this.numerator > this.denominator && this.denominator != 1 && this.getNumerator() > 0 && this.getDenominator() > 0) {
            int num = numerator / denominator;
            return num + "'" + numerator % denominator + "/" + denominator;
        } else if (denominator == 1) {
            return numerator.toString();
        } else return numerator + "/" + denominator;
    }
}

 

  

public class ExpressionResult {

    private String expression;
    private String result;

    @Override
    public String toString() {
        return expression+"="+result;
    }
}

  2. 很多人采用二叉樹生成表達式或者中綴轉后綴表達式的思路,我則是直接進行表達式的生成與計算,在生成表達式時采用HashSet無法重復的特性來存放表達式達到去重的目的

//表達式的生成,采用HashSet存放表達式,直接去重,可以免去后續檢測是否重復
    public static HashSet<ExpressionResult> generateExpression(Integer r, Integer n) {
        HashSet<ExpressionResult> expressionResultHashSet = new HashSet<>();
        for (int i = 0; i < r; i++) {
            //生成第一個操作符和操作數,在后面計算中使用firstNum存放計算的結果
            char firstOps = GeneratorUtil.getOperator();
            Fraction firstNum = GeneratorUtil.getFraction(n);
            char secondOps = firstOps;
            Fraction secondNum = firstNum;
            ExpressionResult expressionResult = new ExpressionResult();
            StringBuilder expression = new StringBuilder().append(firstNum);
            //生成后續兩個操作符並進行表達式的拼接
            for (int j = 0; j < 2; j++) {
                //獲取第二個操作數
                secondNum = GeneratorUtil.getFraction(n);
                switch (secondOps) {
                    //加法則直接進行拼接,不需要額外操作
                    case '+':
                        //將當前運算符保存,后面在優先級比較中會使用到(下同)
                        firstOps = secondOps;
                        expression.append(secondOps).append(secondNum);
                        //保存運算的中間結果(下同)
                        firstNum = Calculator.ADD(firstNum, secondNum);
                        //獲取下一個操作符(下同)
                        secondOps = GeneratorUtil.getOperator();
                        break;
                    case '-':
                        firstOps = secondOps;
                        //由於不能產生負數,所以在減法時要進行比較,如果前數小於后數則將表達式倒置拼接
                        if (Calculator.CMP(firstNum, secondNum)) {
                            firstNum = Calculator.SUB(firstNum, secondNum);
                            expression.append(secondOps).append(secondNum);
                        } else {
                            expression = new StringBuilder().append(secondNum).append(secondOps).append(expression);
                            firstNum = Calculator.SUB(secondNum, firstNum);
                        }
                        secondOps = GeneratorUtil.getOperator();
                        break;
                    case '×':
                        //乘法優先級大,在這里判斷前面是否有優先級較小的加減操作,有的話將表達式帶上括號再拼接乘法運算
                        if (firstOps == '+' || firstOps == '-') {
                            expression = new StringBuilder().append("(").append(expression).append(")").append(secondOps).append(secondNum);
                        } else {
                            expression.append(secondOps).append(secondNum);
                        }
                        //保存運算結果
                        firstNum = Calculator.MUL(firstNum, secondNum);
                        firstOps = secondOps;
                        secondOps = GeneratorUtil.getOperator();
                        break;
                    case '÷':
                        //除法優先級大,在這里判斷前面是否有優先級較小的加減操作,有的話將表達式帶上括號再拼接乘法運算
                        if (firstOps == '+' || firstOps == '-') {
                            expression = new StringBuilder().append("(").append(expression).append(")").append(secondOps).append(secondNum);
                            firstNum = Calculator.DIV(secondNum, firstNum);
                        } else {
                            expression.append(secondOps).append(secondNum);
                            firstNum = Calculator.DIV(firstNum, secondNum);
                        }
                        firstOps = secondOps;
                        secondOps = GeneratorUtil.getOperator();
                        break;
                }
            }
            //將表達式和結果保存,放進HashSet
            expressionResult.setExpression(expression.toString());
            expressionResult.setResult(firstNum.toString());
            expressionResultHashSet.add(expressionResult);
        }
        return expressionResultHashSet;
    }

 

  3. 隨機數和操作符的獲取

public class GeneratorUtil {

    private static final char OPERATORS[] = {'+', '-', '×', '÷'};
    private static final Random R = new Random();

    public static Fraction getFraction(int maximum) {
        //調整隨機數為整數或者分數
        boolean isFraction = R.nextBoolean();
        return isFraction ? new Fraction(R.nextInt(maximum) + 1, R.nextInt(maximum) + 1) : new Fraction(R.nextInt(maximum) + 1, 1);
    }

    public static char getOperator() {
        return OPERATORS[R.nextInt(4)];
    }

}

 

  4. 分數的運算操作

public class Calculator {

    /**
     * 化簡
     */
    public static Integer simplify(Integer numerator, Integer denominator) {
        if (denominator == 0) return numerator;
        return numerator % denominator == 0 ? denominator : simplify(denominator, numerator % denominator);
    }

    //相加
    public static Fraction ADD(Fraction first, Fraction second) {
        return new Fraction(first.getNumerator() * second.getDenominator() + first.getDenominator() * second.getNumerator(),
        first.getDenominator() * second.getDenominator());
    }

    //相減
    public static Fraction SUB(Fraction first, Fraction second) {
        return new Fraction(first.getNumerator() * second.getDenominator() - first.getDenominator() * second.getNumerator(), first.getDenominator() * second.getDenominator());
    }

    //相乘
    public static Fraction MUL(Fraction first, Fraction second) {
        return new Fraction(first.getNumerator() * second.getNumerator(), first.getDenominator() * second.getDenominator());
    }

    //相除
    public static Fraction DIV(Fraction first, Fraction second) {
        return MUL(first, Countdown(second));
    }
    //取倒
    public static Fraction Countdown(Fraction fraction) {
        return new Fraction(fraction.getDenominator(), fraction.getNumerator());
    }

    //比較大小
    public static boolean CMP(Fraction first, Fraction second) {
        Fraction result = DIV(first, second);
        return result.getNumerator() > result.getDenominator() && result.getNumerator() > 0 ? true : false;
    }
    //獲取最大公因數並約去(輾轉相除法)
    public static int GCD(int a, int b) {
        if (b == 0) return a;
        return a % b == 0 ? b : GCD(b, a % b);
    }
}

 

  5. 題目、答案文件和答案比對結果文件的生成

public static void generateFile(HashSet<ExpressionResult> expressionResultHashSet) throws IOException {
        File questionFile = new File("Exercises.txt");
        File answerFile = new File("Answers.txt");
        if (!questionFile.exists()) {
            questionFile.createNewFile();
        }
        if (!answerFile.createNewFile()) {
            answerFile.createNewFile();
        }
        try (BufferedWriter abw = new BufferedWriter(new FileWriter(answerFile));
             BufferedWriter qbw = new BufferedWriter(new FileWriter(questionFile))) {

            int count = 1;
            for (ExpressionResult e : expressionResultHashSet) {
                try {
                    qbw.write(count + "." + e.getExpression());
                    qbw.newLine();
                    abw.write(count + "." + e.getResult());
                    abw.newLine();
            //將表達式放入隊列,在監聽線程中拼接到界面中去
                    GuiForOperator.queue.add(count + "." + e.getExpression() + "=" + e.getResult());
                    count++;
                } catch (IOException e1) {
                    e1.printStackTrace();
                }
            }

        }
    }

 

public static void CompareAnswers(File answerFile) throws IOException {
        List<String> correctList = new ArrayList<>();
        List<String> wrongList = new ArrayList<>();
        try (BufferedReader qrAnswer = new BufferedReader(new FileReader(answerFile));
             BufferedReader qrExercise = new BufferedReader(new FileReader("Answers.txt"))) {
            String eStr = null;
            String aStr = null;
            while ((eStr = qrExercise.readLine()) != null && (aStr = qrAnswer.readLine()) != null) {
                String orderNum = eStr.substring(0, eStr.indexOf("."));
                String standardAnswer = aStr.substring(2, aStr.length());
                String submitAnswer = eStr.substring(2, eStr.length());
                if (standardAnswer.equals(submitAnswer)) {
                    correctList.add(orderNum);
                } else {
                    wrongList.add(orderNum);
                }
            }
        }
        File gradeFile = new File("Grade.txt");
        if (!gradeFile.exists()) {
            gradeFile.createNewFile();
        }
        try (BufferedWriter bw = new BufferedWriter(new FileWriter(gradeFile))) {
            StringBuilder correctStr = new StringBuilder().append("Correct: ").append(correctList.size()).append(" (");
            StringBuilder wrongStr = new StringBuilder().append("Wrong: ").append(wrongList.size()).append(" (");
            correctList.forEach((e) -> {
                correctStr.append(e + ",");
            });
            wrongList.forEach((e) -> {
                wrongStr.append(e + ",");
            });
            bw.write(correctStr.toString().substring(0, correctStr.lastIndexOf(",")) + ")");
            bw.newLine();
            if (wrongList.size() != 0) {
                bw.write(wrongStr.toString().substring(0, wrongStr.lastIndexOf(",")) + ")");
            } else {
                bw.write(wrongStr.append(")").toString());
            }
       //將比對結果放入隊列,在監聽線程中拼接到界面中去
            GuiForOperator.queue.add(correctStr.toString().substring(0, correctStr.lastIndexOf(",")) + ")");
            GuiForOperator.queue.add(wrongStr.toString().substring(0, wrongStr.lastIndexOf(",")) + ")");

        }
    }

  6. 界面:包括傳入參數、文件以及結果顯示

public class GuiForOperator extends JFrame {
   // 使用隊列存放表達式,起線程監聽,有則取出並顯示
    public static BlockingQueue<String> queue = new LinkedBlockingQueue<>();

    private JPanel contentPane;
    private JTextField textField;
    private JTextField textField_1;
    public JTextArea textArea;
    public JScrollPane scrollPane;


    /**
     * Create the frame.
     */
    public GuiForOperator() {
        setTitle("\u56DB\u5219\u8FD0\u7B97");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setBounds(100, 100, 706, 495);
        contentPane = new JPanel();
        contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
        setContentPane(contentPane);
        contentPane.setLayout(null);

        JLabel label = new JLabel("\u9898\u76EE\u6570\u91CF\uFF1A");
        label.setBounds(55, 43, 76, 18);
        contentPane.add(label);

        textField = new JTextField();
        textField.setBounds(163, 35, 282, 35);
        contentPane.add(textField);
        textField.setColumns(10);

        JLabel label_1 = new JLabel("\u6700\u5927\u968F\u673A\u6570\uFF1A");
        label_1.setBounds(55, 91, 125, 18);
        contentPane.add(label_1);

        textField_1 = new JTextField();
        textField_1.setBounds(163, 83, 282, 35);
        contentPane.add(textField_1);
        textField_1.setColumns(10);


        scrollPane = new JScrollPane(textArea);
        textArea = new JTextArea();
        textArea.setEditable(false);
        textArea.setBounds(55, 167, 591, 255);
        textArea.setLineWrap(true);
        scrollPane.setBounds(55, 167, 591, 255);
        scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
        contentPane.add(scrollPane);
        scrollPane.setViewportView(textArea);


        JButton button = new JButton("\u786E\u5B9A");
        button.addActionListener(e -> {
            String r = textField.getText();
            String n = textField_1.getText();
            textArea.setText("");
            try {
                //傳入參數生成表達式寫入文件
                HashSet<ExpressionResult> expressionResults = Generator.generateExpression(Integer.valueOf(r), Integer.valueOf(n));
                Generator.generateFile(expressionResults);
            } catch (IOException e1) {
                e1.printStackTrace();
            }
        });
        button.setBounds(533, 87, 113, 27);
        contentPane.add(button);
        JButton btnNewButton = new JButton("選擇文件");
        btnNewButton.addActionListener(arg0 -> {

            JFileChooser jfc = new JFileChooser();
            jfc.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
            jfc.showDialog(new JLabel(), "選擇");
            File file = jfc.getSelectedFile();//得到文件
            if (file.isDirectory()) {
                System.out.println("文件夾:" + file.getAbsolutePath());
            } else if (file.isFile()) {
                System.out.println("文件:" + file.getAbsolutePath());
            }
            try {
                //傳入比對文件
                Generator.CompareAnswers(file);
            } catch (IOException e) {
                e.printStackTrace();
            }
        });
        btnNewButton.setBounds(533, 39, 113, 27);
        contentPane.add(btnNewButton);
    }

  7. 程序入口

public class AppEntry {
    public static void main(String[] args) {
        //新建窗口並顯示
        GuiForOperator frame = new GuiForOperator();
        frame.setVisible(true);
        //啟動線程監聽隊列取出表達式進行顯示
        new Thread(() -> {
            while (true) {
                try {
                    String expression = GuiForOperator.queue.take();
                    if (expression != null) {
                        frame.textArea.append(expression + "\r\n");
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}

 

 

五. 運行耗時

  生成一萬道題目,數值在一萬以內;生成十萬道題目,數值在十萬以內;生成一百萬道題目,數值在一百萬以內;

  

六. 心得體會

       蔡海傑:一開始用IDEA寫程序的圖示化界面,發現布局與組件排序很亂且滿足不了需求,后面用了eclipse的Windowsbuilder插件,一切迎刃而解。

  黃梓塏:在表達式生成的方式上,原來也是想采用中綴轉后綴的方式,但是感覺比較麻煩,所以有點投機取巧,在生成時采用倒置避免負數,使用括號來避免優先級問題,在生成的同時也得出運算結果,所以這里耗時相對少一點;在查重上原本是想另寫方法,但是發現hashset可以直接避免重復,更為簡便,感覺對java的數據結構了解還不是很全面,后面可能要多加強一下。

 

   


免責聲明!

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



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