四则运算
一 . 项目介绍
项目仓库: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(); } }
参考资料:https://www.cnblogs.com/hzkkk/p/9727038.html