四则运算 Java (于泽浩,袁浩越)

GitHub 地址

一. 项目要求

题目 实现一个自动生成小学四则运算题目的命令行程序。
需求(全部完成)

  1. 使用 -n 参数控制生成题目的个数
    Myapp.exe -n 10
  2. 使用 -r 参数控制题目中数值(自然数、真分数和真分数分母)的范围
    Myapp.exe -r 10
  3. 生成的题目中计算过程不能产生负数,也就是说算术表达式中如果存在形如e1 − e2的子表达式,那么e1 ≥ e2。
  4. 生成的题目中如果存在形如e1 ÷ e2的子表达式,那么其结果应是真分数。
  5. 每道题目中出现的运算符个数不超过3个。
  6. 程序一次运行生成的题目不能重复,即任何两道题目不能通过有限次交换+和×左右的算术表达式变换为同一道题目。即不能通过有限次变换变成同一道题目,下面例子均为重复题目。
  • 23+45 与 45+23
  • 3+(2+1)与1+2+3

生成的题目存入执行程序的当前目录下的Exercises.txt文件,格式如下:

  1. 四则运算题目 1
  2. 四则运算题目 2
    ……
    真分数五分之三表示为3/5,真分数二又八分之三表示为2’3/8
  1. 在生成题目的同时,计算出所有题目的答案,并存入执行程序的当前目录下的Answers.txt文件,格式如下
  1. 答案1
  2. 答案2
    特别的,真分数的运算如下例所示
    1/6 + 1/8 = 7/24
  1. 程序应能支持一万道题目的生成。
  2. 程序支持对给定的题目文件和答案文件,判定答案中的对错并进行数量统计,结果输出至 Grade.txt
    Myapp.exe -e <exercisefile>.txt -a <answerfile>.txt
    格式如下

Correct: 5 (1, 3, 5, 7, 9)

二. PSP

三. 思路分析

生成题目:

使用随机函数随机决定运算符个数,运算符类型,数值是否为分数(真分数或假分数),是否包含括号等因素;

在生成数值时,统一先使用 apache 类库中的 Fraction 分数类来封装,以方面后面的计算;

每循环生成好一道题目,便调用方法生成答案,生成答案时利用递归的方式一步步算出最终答案,并将题目与答案一同封装进 Question 类中,生成答案时遇到负数,选择跳过重新生成题目;

检查重复题目,会根据题目的运算符的优先计算进行重新排序,并将同一个运算符的两个数值根据大小的关系进行了排序,再进行查重。

核对答案:

这个功能需要读取两个文件,读取题目文件时,计算出题目的正确答案,最后读取答案文件,将两者对比即可。

四. 代码说明

主要的类

  1. Main 启动类
  2. ExerciseService 生成题目与答案 (-n -r 命令)
  3. CheckService 读取答案进行比对 (-e -a 命令)

Main.java 启动函数


    public static void main(String[] args) throws IOException {
        int questions, range;
        //匹配命令 -n -r
        if (Arrays.stream(args).anyMatch("-n"::equals) && Arrays.stream(args).anyMatch("-r"::equals)) {
            questions = Integer.valueOf(args[1]);
            range = Integer.valueOf(args[3]) - 1;
            //生成题目
            ExerciseSevice.generateExercise(range, questions);
            System.out.println("生成题目完毕!");
            //匹配命令 -e -a
        } else if (Arrays.stream(args).anyMatch("-e"::equals) && Arrays.stream(args).anyMatch("-a"::equals)) {
            //核对答案
            CheckSevice.checkExercise(args[1], args[3]);
            System.out.println("结果生成成功!");
        } else {
            System.out.println("错误命令!");
        }
    }


ExerciseService 生成题目和答案

@SuppressWarnings("unchecked")
public class ExerciseSevice {
    //存放题目与答案的列表
    public static List<Question> questionsList = new ArrayList<>();
    //是否是负数
    private static boolean isNegative = false;
    //按计算顺序记录每步的答案
    private static List<Fraction> answerList = new ArrayList<>();
    //按计算顺序记录每个操作符
    private static List<String> operatorList = new ArrayList<>();

    /**
     * 生成题目与答案,并写入文件
     *
     * @param range     取值范围
     * @param questions 题目总数
     */
    public static void generateExercise(int range, int questions) throws IOException {
        //生成题目
        ExerciseSevice.getQuestion(questions, range);
        BufferedWriter exerciseWriter = new BufferedWriter(new FileWriter(Main.EXERCISE_PATH));
        BufferedWriter answerWriter = new BufferedWriter(new FileWriter(Main.ANSWER_PATH));
        //清空文件
        exerciseWriter.flush();
        answerWriter.flush();
        //分别写入题目与答案于两个文件中
        int currentQuestion = 1;
        for (Question question : questionsList) {
            String answer = question.getAnswer();
            exerciseWriter.write("第" + currentQuestion + "题:" + question.toString() + " =");
            exerciseWriter.newLine();
            answerWriter.write(currentQuestion + ". " + answer);
            answerWriter.newLine();
            currentQuestion++;
        }
        //结束,关闭流
        exerciseWriter.close();
        answerWriter.close();
    }

    /**
     * 随机生成题目与对应的答案, 存入 questionsList
     */
    public static void getQuestion(int questionsSum, int range) {
        //循环随机生成题目
        while (questionsList.size() < questionsSum) {
            List questionList = new LinkedList();
            //该表达式是否包含括号
            boolean havebrackets = false;
            Random random = new Random();
            //随机生成该题目的运算符个数
            int operatorSum = random.nextInt(3) + 1;
            int currentOperatorNum = 0;
            while (currentOperatorNum < operatorSum) {
                List list = new LinkedList();
                //生成运算符
                String operator = Util.getOperator();
                //决定是否为分数
                if (random.nextBoolean()) {
                    //生成分数
                    Fraction fraction = Util.getFraction(range);
                    list.add(fraction);
                    if (havebrackets) {
                        list.add(")");
                    }
                    list.add(operator);
                } else {
                    //生成整数
                    int num = random.nextInt(range) + 1;
                    list.add(new Fraction(num, 1));
                    if (havebrackets) {
                        list.add(")");
                    }
                    list.add(operator);
                }
                currentOperatorNum++;
                //处理括号
                if (havebrackets) {
                    havebrackets = false;
                    questionList.addAll(list);
                    continue;
                }
                //如果是乘除法,则不考虑括号
                if ("×".equals(operator) || "÷".equals(operator) || operatorSum == 1) {
                    questionList.addAll(list);
                    continue;
                }
                //如果是加减法,决定是否存在括号
                if (random.nextBoolean()) {
                    list.add(0, "(");
                    havebrackets = true;
                }
                questionList.addAll(list);
            }
            if (random.nextBoolean()) {
                Fraction fraction = Util.getFraction(range);
                questionList.add(fraction);
            } else {
                int num = random.nextInt(range) + 1;
                questionList.add(new Fraction(num, 1));
            }
            if (havebrackets) {
                questionList.add(")");
            }
            //生成题目答案
            Fraction answer = getAnswer(new LinkedList(questionList));
            if (isNegative) {
                isNegative = false;
                answerList.clear();
                operatorList.clear();
                continue;
            }
            Question question = new Question(new ArrayList<>(answerList),
                    new ArrayList<>(operatorList), questionList, Util.getNum(answer).toString());
            boolean isRepeat = false;
            //检查是否重复
            if (questionsList.parallelStream().anyMatch(qf -> qf.equals(question))) {
                answerList.clear();
                operatorList.clear();
                isRepeat = true;
            }
            if (!isRepeat) {
                questionsList.add(question);
            }
        }
    }

    /**
     * 生成题目答案
     *
     * @param questionList 题目
     */
    static Fraction getAnswer(List questionList) {
        //优先处理括号里的表达式
        while (questionList.contains("(")) {
            int h1 = questionList.indexOf("(");
            int h2 = questionList.indexOf(")");
            //获得括号内的表达式
            List havebracketsList = questionList.subList(h1 + 1, h2);
            //安置计算出来的答案
            questionList.add(h2 + 1, getAnswer(havebracketsList));
            //移除原表达式,以进行下一步计算
            for (int i = 0; i < 5; i++) {
                questionList.remove(h1);
            }
        }
        //先计算乘除法
        while ((questionList.contains("×") || questionList.contains("÷")) && !isNegative) {
            int multiply = questionList.indexOf("×");
            int divide = questionList.indexOf("÷");
            if (multiply < divide && multiply != -1 || divide == -1) {
                //计算乘法
                questionList = calculate(questionList, multiply, Fraction::multiply);
                operatorList.add("×");
            } else {
                //计算除法
                questionList = calculate(questionList, divide, Fraction::divide);
                operatorList.add("÷");
            }
        }
        //后计算加减法
        while ((questionList.contains("+") || questionList.contains("-")) && !isNegative) {
            int add = questionList.indexOf("+");
            int subtract = questionList.indexOf("-");
            if (add < subtract && add != -1 || subtract == -1) {
                //计算加法
                questionList = calculate(questionList, add, Fraction::add);
                operatorList.add("+");
            } else {
                //计算减法
                questionList = calculate(questionList, subtract, Fraction::subtract);
                operatorList.add("-");
            }
        }
        //返回答案
        return (Fraction) questionList.get(0);
    }

    /**
     * 计算题目其中一个表达式
     *
     * @param list               题目中的数字运算符序列
     * @param index              表达式中运算符所在位置
     * @param fractionArithmetic 四则运算计算函数接口
     */
    private static List calculate(List list, int index, FractionArithmetic fractionArithmetic) {
        //计算数值1
        Fraction fraction1 = (Fraction) list.get(index - 1);
        //计算数值2
        Fraction fraction2 = (Fraction) list.get(index + 1);
        //计算结果
        Fraction fraction = fractionArithmetic.arithmetic(fraction1, fraction2);
        //如果计算的结果是负数
        if (fraction.getNumerator() <= 0) {
            isNegative = true;
        }
        answerList.add(fraction);
        if (list.size() == 3) {
            return new LinkedList(Collections.singletonList(fraction));
        } else {
            list.remove(index);
            list.remove(index);
            list.remove(index - 1);
            list.add(index - 1, fraction);
            return list;
        }
    }
}

CheckService 核查答案

@SuppressWarnings("unchecked")
public class CheckSevice {
    //正确题目个数
    private static int correct = 0;
    //错误题目个数
    private static int wrong = 0;
    //正确题目列表
    private static List<Integer> correctList = new ArrayList<>();
    //错误题目列表
    private static List<Integer> wrongList = new ArrayList<>();

    /**
     * 比对题目与答案的正确与否
     *
     * @param exercisePath 题目文件路径
     * @param answerPath   答案文件路径
     */
    public static void checkExercise(String exercisePath, String answerPath) throws IOException {
        BufferedReader exerciseReader = new BufferedReader(new FileReader(exercisePath));
        List<String> operatorList = new ArrayList<>(Arrays.asList("+", "-", "×", "÷", "(", ")"));
        //读取题目文件,并生成题目的答案
        List<Fraction> realAnswerList = exerciseReader.lines()
                .map(s -> s.substring(s.indexOf(":") + 1, s.indexOf("=")).trim())
                .map(s -> s.split("\\s+"))
                .map(array -> {
                    List questionList = new ArrayList();
                    for (String s : array) {
                        //转换为分数
                        if (!operatorList.contains(s)) {
                            questionList.add(Util.getFraction(s));
                        } else {
                            questionList.add(s);
                        }
                    }
                    return questionList;
                })
                .map(ExerciseSevice::getAnswer)
                .collect(toList());
        //读取答案文件
        BufferedReader answerReader = new BufferedReader(new FileReader(answerPath));
        List<Fraction> answerList = answerReader.lines()
                .map(s -> s.substring(s.indexOf(".") + 1).trim())
                .map(Util::getFraction)
                .collect(toList());
        //比对答案与正确答案,生成结果
        getCheckResult(realAnswerList, answerList);
        //将结果写入文件中
        BufferedWriter gradeWriter = new BufferedWriter(new FileWriter(Main.GRADE_PATH));
        gradeWriter.flush();
        gradeWriter.write("Correct: " + correct + " " + correctList);
        gradeWriter.newLine();
        gradeWriter.write("Wrong: " + wrong + " " + wrongList);
        //结束,关闭流
        gradeWriter.close();
    }

    /**
     * 比对答案
     *
     * @param realAnswerList 正确答案
     * @param answerList     输入的答案
     */
    private static void getCheckResult(List<Fraction> realAnswerList, List<Fraction> answerList) {
        for (int i = 0; i < realAnswerList.size(); i++) {
            //题目正确
            if (realAnswerList.get(i).equals(answerList.get(i))) {
                correct++;
                correctList.add(i + 1);
            } else {
                //题目错误
                wrong++;
                wrongList.add(i + 1);
            }
        }
    }
}

五. 测试运行

public class ExerciseTest {
    static Map<Integer, Integer> map = new HashMap<>();
    static {
        map.put(10000, 10);
        map.put(1000, 5);
        map.put(10, 5);
        map.put(20, 10);
        map.put(30, 20);
        map.put(50, 10);
        map.put(100, 10);
        map.put(200, 10);
        map.put(300, 3);
        map.put(120, 5);
    }

    @Test
    public void main() throws Exception {
        for (int questionSum : map.keySet()) {
            ExerciseSevice.getQuestion(questionSum, map.get(questionSum));
            int currentQuestion = 1;
            for (Question question : ExerciseSevice.questionsList) {
                String answer = question.getAnswer();
                System.out.println("第" + currentQuestion + "题:" + question.toString() + " = " + answer);
                currentQuestion++;
            }
        }

    }
}

public class CheckTest {
    private final static String PROJECT_PATH = new File("").getAbsolutePath();
    private final static String EXERCISE_PATH = PROJECT_PATH + "\\src\\main\\resources\\exercise.txt";
    private final static String ANSWER_PATH = PROJECT_PATH + "\\src\\main\\resources\\answers.txt";

    @Test
    public void main() throws Exception {
        CheckSevice.checkExercise(EXERCISE_PATH, ANSWER_PATH);
    }
}

六. 总结

本次结对编程由两个人共同合作,两个人在项目的进行中进行了很多交流,统一了主要思路,但是在实际开发中仍然遇到了不少问题,也一起进行了很多的思考。这次的编程,懂得了如何去合作,去交流,大大提高了开发效率。

posted @ 2018-09-30 16:34  HowieYuan  阅读(265)  评论(0)    收藏  举报