四则运算

一. 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 }

 

 

七. 性能分析

 

生成一万道题目,数值在一万以内;生成十万道题目,数值在十万以内;生成一百万道题目,数值在一百万以内;

 

  四则运算

 

上一篇:Android设计实现一个简单计算器


下一篇:OpenSSL漏洞披露:想法没错 时间不对