一. PSP表格
二. 项目要求:
- 能自动生成小学四则运算题目
- 除了整数以外,还要支持真分数的四则运算
三. 解题思路:
- 了解四则运算的基本法则
- 利用随机函数随机生成数字以及运算符
- 用户输入答案程序需要判断答案是否正确
- 支持真分数运算
四. 符号说明:
自然数:0, 1, 2, …。
- 真分数:1/2, 1/3, 2/3, 1/4, 1’1/2, …。
- 运算符:+, −, ×, ÷。
- 括号:(, )。
- 等号:=。
- 分隔符:空格(用于四则运算符和等号前后)。
- 算术表达式: e = n | e1 + e2 | e1 − e2 | e1 × e2 | e1 ÷ e2 | (e),
其中e, e1和e2为表达式,n为自然数或真分数。
- 四则运算题目:e = ,其中e为算术表达式。
五. 项目功能
由界面输入参数,实现了题目的生成以及去重,问题与答案的文件保存,用户输入答案文件与标准答案的校对以及结果文件生成
运行示例(Answerfile.txt为用户提交文件):
六. 代码设计
1. 由于题目设计真分数运算,所以采用分数作为基本的运算单位。使用ExpressionResult类保存每条表达式以及它的运算结果
1 public class Fraction { 2 //分子 3 private Integer numerator; 4 //分母 5 private Integer denominator; 6 7 //取最大公因数化简 8 public Fraction(Integer n, Integer d) { 9 Integer GCD = Calculator.GCD(n, d); 10 this.numerator = n /= GCD; 11 this.denominator = d /= GCD; 12 } 13 14 15 //重写toString方法,以真分数形式表示 16 @Override 17 public String toString() { 18 if (this.numerator > this.denominator && this.denominator != 1 && this.getNumerator() > 0 && this.getDenominator() > 0) { 19 int num = numerator / denominator; 20 return num + "'" + numerator % denominator + "/" + denominator; 21 } else if (denominator == 1) { 22 return numerator.toString(); 23 } else return numerator + "/" + denominator; 24 } 25 }
1 public class ExpressionResult { 2 3 private String expression; 4 private String result; 5 6 @Override 7 public String toString() { 8 return expression+"="+result; 9 } 10 }
2. 很多人采用二叉树生成表达式或者中缀转后缀表达式的思路,我则是直接进行表达式的生成与计算,在生成表达式时采用HashSet无法重复的特性来存放表达式达到去重的目的
1 //表达式的生成,采用HashSet存放表达式,直接去重,可以免去后续检测是否重复 2 public static HashSet<ExpressionResult> generateExpression(Integer r, Integer n) { 3 HashSet<ExpressionResult> expressionResultHashSet = new HashSet<>(); 4 for (int i = 0; i < r; i++) { 5 //生成第一个操作符和操作数,在后面计算中使用firstNum存放计算的结果 6 char firstOps = GeneratorUtil.getOperator(); 7 Fraction firstNum = GeneratorUtil.getFraction(n); 8 char secondOps = firstOps; 9 Fraction secondNum = firstNum; 10 ExpressionResult expressionResult = new ExpressionResult(); 11 StringBuilder expression = new StringBuilder().append(firstNum); 12 //生成后续两个操作符并进行表达式的拼接 13 for (int j = 0; j < 2; j++) { 14 //获取第二个操作数 15 secondNum = GeneratorUtil.getFraction(n); 16 switch (secondOps) { 17 //加法则直接进行拼接,不需要额外操作 18 case '+': 19 //将当前运算符保存,后面在优先级比较中会使用到(下同) 20 firstOps = secondOps; 21 expression.append(secondOps).append(secondNum); 22 //保存运算的中间结果(下同) 23 firstNum = Calculator.ADD(firstNum, secondNum); 24 //获取下一个操作符(下同) 25 secondOps = GeneratorUtil.getOperator(); 26 break; 27 case '-': 28 firstOps = secondOps; 29 //由于不能产生负数,所以在减法时要进行比较,如果前数小于后数则将表达式倒置拼接 30 if (Calculator.CMP(firstNum, secondNum)) { 31 firstNum = Calculator.SUB(firstNum, secondNum); 32 expression.append(secondOps).append(secondNum); 33 } else { 34 expression = new StringBuilder().append(secondNum).append(secondOps).append(expression); 35 firstNum = Calculator.SUB(secondNum, firstNum); 36 } 37 secondOps = GeneratorUtil.getOperator(); 38 break; 39 case '×': 40 //乘法优先级大,在这里判断前面是否有优先级较小的加减操作,有的话将表达式带上括号再拼接乘法运算 41 if (firstOps == '+' || firstOps == '-') { 42 expression = new StringBuilder().append("(").append(expression).append(")").append(secondOps).append(secondNum); 43 } else { 44 expression.append(secondOps).append(secondNum); 45 } 46 //保存运算结果 47 firstNum = Calculator.MUL(firstNum, secondNum); 48 firstOps = secondOps; 49 secondOps = GeneratorUtil.getOperator(); 50 break; 51 case '÷': 52 //除法优先级大,在这里判断前面是否有优先级较小的加减操作,有的话将表达式带上括号再拼接乘法运算 53 if (firstOps == '+' || firstOps == '-') { 54 expression = new StringBuilder().append("(").append(expression).append(")").append(secondOps).append(secondNum); 55 firstNum = Calculator.DIV(secondNum, firstNum); 56 } else { 57 expression.append(secondOps).append(secondNum); 58 firstNum = Calculator.DIV(firstNum, secondNum); 59 } 60 firstOps = secondOps; 61 secondOps = GeneratorUtil.getOperator(); 62 break; 63 } 64 } 65 //将表达式和结果保存,放进HashSet 66 expressionResult.setExpression(expression.toString()); 67 expressionResult.setResult(firstNum.toString()); 68 expressionResultHashSet.add(expressionResult); 69 } 70 return expressionResultHashSet; 71 }
3. 随机数和操作符的获取
1 public class GeneratorUtil { 2 3 private static final char OPERATORS[] = {'+', '-', '×', '÷'}; 4 private static final Random R = new Random(); 5 6 public static Fraction getFraction(int maximum) { 7 //调整随机数为整数或者分数 8 boolean isFraction = R.nextBoolean(); 9 return isFraction ? new Fraction(R.nextInt(maximum) + 1, R.nextInt(maximum) + 1) : new Fraction(R.nextInt(maximum) + 1, 1); 10 } 11 12 public static char getOperator() { 13 return OPERATORS[R.nextInt(4)]; 14 } 15 16 }
4. 分数的运算操作
1 public class Calculator { 2 3 /** 4 * 化简 5 */ 6 public static Integer simplify(Integer numerator, Integer denominator) { 7 if (denominator == 0) return numerator; 8 return numerator % denominator == 0 ? denominator : simplify(denominator, numerator % denominator); 9 } 10 11 //相加 12 public static Fraction ADD(Fraction first, Fraction second) { 13 return new Fraction(first.getNumerator() * second.getDenominator() + first.getDenominator() * second.getNumerator(), 14 first.getDenominator() * second.getDenominator()); 15 } 16 17 //相减 18 public static Fraction SUB(Fraction first, Fraction second) { 19 return new Fraction(first.getNumerator() * second.getDenominator() - first.getDenominator() * second.getNumerator(), first.getDenominator() * second.getDenominator()); 20 } 21 22 //相乘 23 public static Fraction MUL(Fraction first, Fraction second) { 24 return new Fraction(first.getNumerator() * second.getNumerator(), first.getDenominator() * second.getDenominator()); 25 } 26 27 //相除 28 public static Fraction DIV(Fraction first, Fraction second) { 29 return MUL(first, Countdown(second)); 30 } 31 //取倒 32 public static Fraction Countdown(Fraction fraction) { 33 return new Fraction(fraction.getDenominator(), fraction.getNumerator()); 34 } 35 36 //比较大小 37 public static boolean CMP(Fraction first, Fraction second) { 38 Fraction result = DIV(first, second); 39 return result.getNumerator() > result.getDenominator() && result.getNumerator() > 0 ? true : false; 40 } 41 //获取最大公因数并约去(辗转相除法) 42 public static int GCD(int a, int b) { 43 if (b == 0) return a; 44 return a % b == 0 ? b : GCD(b, a % b); 45 } 46 }
5. 题目、答案文件和答案比对结果文件的生成
1 public static void generateFile(HashSet<ExpressionResult> expressionResultHashSet) throws IOException { 2 File questionFile = new File("Exercises.txt"); 3 File answerFile = new File("Answers.txt"); 4 if (!questionFile.exists()) { 5 questionFile.createNewFile(); 6 } 7 if (!answerFile.createNewFile()) { 8 answerFile.createNewFile(); 9 } 10 try (BufferedWriter abw = new BufferedWriter(new FileWriter(answerFile)); 11 BufferedWriter qbw = new BufferedWriter(new FileWriter(questionFile))) { 12 13 int count = 1; 14 for (ExpressionResult e : expressionResultHashSet) { 15 try { 16 qbw.write(count + "." + e.getExpression()); 17 qbw.newLine(); 18 abw.write(count + "." + e.getResult()); 19 abw.newLine(); 20 //将表达式放入队列,在监听线程中拼接到界面中去 21 GuiForOperator.queue.add(count + "." + e.getExpression() + "=" + e.getResult()); 22 count++; 23 } catch (IOException e1) { 24 e1.printStackTrace(); 25 } 26 } 27 28 } 29 }
1 public static void CompareAnswers(File answerFile) throws IOException { 2 List<String> correctList = new ArrayList<>(); 3 List<String> wrongList = new ArrayList<>(); 4 try (BufferedReader qrAnswer = new BufferedReader(new FileReader(answerFile)); 5 BufferedReader qrExercise = new BufferedReader(new FileReader("Answers.txt"))) { 6 String eStr = null; 7 String aStr = null; 8 while ((eStr = qrExercise.readLine()) != null && (aStr = qrAnswer.readLine()) != null) { 9 String orderNum = eStr.substring(0, eStr.indexOf(".")); 10 String standardAnswer = aStr.substring(2, aStr.length()); 11 String submitAnswer = eStr.substring(2, eStr.length()); 12 if (standardAnswer.equals(submitAnswer)) { 13 correctList.add(orderNum); 14 } else { 15 wrongList.add(orderNum); 16 } 17 } 18 } 19 File gradeFile = new File("Grade.txt"); 20 if (!gradeFile.exists()) { 21 gradeFile.createNewFile(); 22 } 23 try (BufferedWriter bw = new BufferedWriter(new FileWriter(gradeFile))) { 24 StringBuilder correctStr = new StringBuilder().append("Correct: ").append(correctList.size()).append(" ("); 25 StringBuilder wrongStr = new StringBuilder().append("Wrong: ").append(wrongList.size()).append(" ("); 26 correctList.forEach((e) -> { 27 correctStr.append(e + ","); 28 }); 29 wrongList.forEach((e) -> { 30 wrongStr.append(e + ","); 31 }); 32 bw.write(correctStr.toString().substring(0, correctStr.lastIndexOf(",")) + ")"); 33 bw.newLine(); 34 if (wrongList.size() != 0) { 35 bw.write(wrongStr.toString().substring(0, wrongStr.lastIndexOf(",")) + ")"); 36 } else { 37 bw.write(wrongStr.append(")").toString()); 38 } 39 //将比对结果放入队列,在监听线程中拼接到界面中去 40 GuiForOperator.queue.add(correctStr.toString().substring(0, correctStr.lastIndexOf(",")) + ")"); 41 GuiForOperator.queue.add(wrongStr.toString().substring(0, wrongStr.lastIndexOf(",")) + ")"); 42 43 } 44 }
6. 界面:包括传入参数、文件以及结果显示
1 public class GuiForOperator extends JFrame { 2 // 使用队列存放表达式,起线程监听,有则取出并显示 3 public static BlockingQueue<String> queue = new LinkedBlockingQueue<>(); 4 5 private JPanel contentPane; 6 private JTextField textField; 7 private JTextField textField_1; 8 public JTextArea textArea; 9 public JScrollPane scrollPane; 10 11 12 /** 13 * Create the frame. 14 */ 15 public GuiForOperator() { 16 setTitle("\u56DB\u5219\u8FD0\u7B97"); 17 setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 18 setBounds(100, 100, 706, 495); 19 contentPane = new JPanel(); 20 contentPane.setBorder(new EmptyBorder(5, 5, 5, 5)); 21 setContentPane(contentPane); 22 contentPane.setLayout(null); 23 24 JLabel label = new JLabel("\u9898\u76EE\u6570\u91CF\uFF1A"); 25 label.setBounds(55, 43, 76, 18); 26 contentPane.add(label); 27 28 textField = new JTextField(); 29 textField.setBounds(163, 35, 282, 35); 30 contentPane.add(textField); 31 textField.setColumns(10); 32 33 JLabel label_1 = new JLabel("\u6700\u5927\u968F\u673A\u6570\uFF1A"); 34 label_1.setBounds(55, 91, 125, 18); 35 contentPane.add(label_1); 36 37 textField_1 = new JTextField(); 38 textField_1.setBounds(163, 83, 282, 35); 39 contentPane.add(textField_1); 40 textField_1.setColumns(10); 41 42 43 scrollPane = new JScrollPane(textArea); 44 textArea = new JTextArea(); 45 textArea.setEditable(false); 46 textArea.setBounds(55, 167, 591, 255); 47 textArea.setLineWrap(true); 48 scrollPane.setBounds(55, 167, 591, 255); 49 scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS); 50 contentPane.add(scrollPane); 51 scrollPane.setViewportView(textArea); 52 53 54 JButton button = new JButton("\u786E\u5B9A"); 55 button.addActionListener(e -> { 56 String r = textField.getText(); 57 String n = textField_1.getText(); 58 textArea.setText(""); 59 try { 60 //传入参数生成表达式写入文件 61 HashSet<ExpressionResult> expressionResults = Generator.generateExpression(Integer.valueOf(r), Integer.valueOf(n)); 62 Generator.generateFile(expressionResults); 63 } catch (IOException e1) { 64 e1.printStackTrace(); 65 } 66 }); 67 button.setBounds(533, 87, 113, 27); 68 contentPane.add(button); 69 JButton btnNewButton = new JButton("选择文件"); 70 btnNewButton.addActionListener(arg0 -> { 71 72 JFileChooser jfc = new JFileChooser(); 73 jfc.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES); 74 jfc.showDialog(new JLabel(), "选择"); 75 File file = jfc.getSelectedFile();//得到文件 76 if (file.isDirectory()) { 77 System.out.println("文件夹:" + file.getAbsolutePath()); 78 } else if (file.isFile()) { 79 System.out.println("文件:" + file.getAbsolutePath()); 80 } 81 try { 82 //传入比对文件 83 Generator.CompareAnswers(file); 84 } catch (IOException e) { 85 e.printStackTrace(); 86 } 87 }); 88 btnNewButton.setBounds(533, 39, 113, 27); 89 contentPane.add(btnNewButton); 90 }
7. 程序入口
1 public class AppEntry { 2 public static void main(String[] args) { 3 //新建窗口并显示 4 GuiForOperator frame = new GuiForOperator(); 5 frame.setVisible(true); 6 //启动线程监听队列取出表达式进行显示 7 new Thread(() -> { 8 while (true) { 9 try { 10 String expression = GuiForOperator.queue.take(); 11 if (expression != null) { 12 frame.textArea.append(expression + "\r\n"); 13 } 14 } catch (InterruptedException e) { 15 e.printStackTrace(); 16 } 17 } 18 }).start(); 19 } 20 }
七. 性能分析
生成一万道题目,数值在一万以内;生成十万道题目,数值在十万以内;生成一百万道题目,数值在一百万以内;