四则运算 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

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

三. 思路分析

生成题目:

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

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

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

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

核对答案:

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

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

四. 代码说明

主要的类

  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);
}
}

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

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

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

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

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

六. 总结

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

上一篇:Day14作业及默写


下一篇:阿里云centos7搭建php+nginx环境