基于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的数据结构了解还不是很全面,后面可能要多加强一下。

 

   

posted @ 2018-09-29 22:56  hzkkk  阅读(3636)  评论(0编辑  收藏