一 . 項目介紹
項目倉庫: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的數據結構了解還不是很全面,后面可能要多加強一下。