结对项目——实现一个自动生成小学四则运算题目的命令行程序—陈泽瀚and林桂旭
软工作业3:结对项目:实现一个自动生成小学四则运算题目的命令行程序
作业属于课程 | 课程首页 - 计科21级1班 - 广东工业大学 - 班级博客 - 博客园 |
---|---|
作业要求 | 个人项目-作业2- 计科21级1班 - 广东工业大学 - 班级博客 - 博客园 |
这个作业的目标 | 实现一个自动生成小学四则运算题目的命令行程序 |
gitee代码仓库
成员信息
姓名 | 学号 |
---|---|
陈泽瀚 | 3121004818 |
林桂旭 | 3121004830 |
PSP表格
PSP2.1 | Personal Software Process Stages |
预估耗时 (分钟) |
实际耗时 (分钟) |
---|---|---|---|
Planning | 计划 | 720 | 1730 |
Estimate | 估计这个任务需要多少时间 | 720 | 1730 |
Development | 开发 | 680 | 300 |
Analysis | 需求分析(包括学习新技术) | 180 | 300 |
Design Spec | 生成设计文档 | 60 | 60 |
Design Review | 设计复审 | 60 | 60 |
Coding Standard | 代码规范(为目前的开发指定合适的规范) | 30 | 100 |
Design | 具体设计 | 60 | 300 |
Coding | 具体编码 | 120 | 300 |
Code Review | 代码复审 | 60 | 60 |
Test | 测试(自我测试,修改代码,提交修改) | 60 | 100 |
Reporting | 报告 | 30 | 100 |
Test Repor | 测试报告 | 30 | 30 |
Size Measurement | 计算工作量 | 30 | 30 |
Postmortem&Process Improvement Plan |
事后总结,并提出过程改进计划 | 30 | 30 |
合计 | 720 | 1730 |
开发工具及依赖
-
编程语言:java
-
IDE:Intellij IDEA 2023.1
-
项目构建工具:maven
-
单元测试:JUnit 4.12
-
性能分析工具:JProfiler 14
-
依赖jar包: junit4.12 & commons-io
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
</dependency>
</dependencies>
设计流程图及接口设计
一,流程图:
生成题目流程图:
判断题目答案流程图:
![](https://img2023.cnblogs.com/blog/3270607/202309/3270607-20230927135600939-1309879687.png)
二,接口设计:
main文件夹:
text文件:
程序需要的文档:
程序实现
一,接口设计:
gitee代码
-
Main:主类,程序入口
-
FileIOUtil:读取文件和输出文件工具类
-
DecimalToFranctionUtil:将小数转化为真分数和带分数的工具类
-
CountUtil:生成题目和计算题目答案的工具类
1.1 FileIoUtil工具类
包含的方法:
核心:调用commons-io提供的方法
// 题目文件:将生成的题目存入文件中
public static void topicToFile(ArrayList<String> topics) throws IOException {
String exercises = "src/txt/Exercises.txt";
//指定文件名
//将题目存入集合中,集合中存放算数表达式的字符串
FileUtils.writeLines(new File(exercises), topics);
}
//存放生成题目的答案
public static void answerToFile(ArrayList<String> answers) throws IOException {
final String answer = "src/txt/Answers.txt";
FileUtils.writeLines(new File(answer), answers);
}
//存放结果文件
public static void gradeToFile(ArrayList<String> list) throws IOException {
final String grade = "src/txt/Grade.txt";
FileUtils.writeLines(new File(grade), list);
}
//读取文件
public static ArrayList<String> readFile(String fileName) throws IOException {
//将内容存放在集合中,按行读取
ArrayList<String> list = (ArrayList<String>) FileUtils.readLines(new File(fileName), "UTF-8");
return list;
}
1.2 CountUtil工具类(核心)
后缀表达式算法参考文献: 逆波兰表达式
包含的方法:
1.createTopic方法:生成一个四则运算(+,-,*,/,( ))
public static String createTopic(int r) {
StringJoiner sj;
while (true) {
sj = new StringJoiner(" ");
// 存放数据
ArrayList<String> number = new ArrayList<>();
//生成的题目为字符串
Random random = new Random();
//获取随机个真分数(0,4)
int franction = random.nextInt(0, 2);
for (int i = 0; i < franction; i++) {
int a = random.nextInt(1, r);
int b = random.nextInt(1, r);
String str;
if (a > b) {
str = b + "/" + a;
} else if (a < b) {
str = a + "/" + b;
} else {
str = "1";
}
number.add(str);
}
// 获取随机整数
for (int i = 0; i < 4 - franction; i++) {
number.add(String.valueOf(random.nextInt(0, r)));
}
// 随机是否加()1:加 0:不加
int flag = random.nextInt(0, 2);
int end = -1;
if (flag == 1) {
//获取在第几位数字加()
flag = random.nextInt(0, 3);
end = flag + 1;
} else {
flag = -1;
}
// 获取随机运算符并拼接
Collections.shuffle(number);
for (int i = 0; i < number.size(); i++) {
if (flag == i) {
sj.add("(");
}
sj.add(number.get(i));
if (end == i) {
sj.add(")");
}
if (i != number.size() - 1) {
sj.add(symbol[random.nextInt(0, 4)]);
}
}
//判断题目是否符合要求
if (checkTopic(sj.toString())) {
break;
}
}
return sj.toString();
}
2.checkTopic方法:
- 检查题目是否符合需求(真分数、结果和过程不产生负数、过程不产生假分数、0不作为除数)
public static boolean checkTopic(String topic) {
//判断除数是否为0
String[] split = topic.split("÷");
for (int i = 1; i < split.length; i++) {
if (String.valueOf(split[i].charAt(1)).equals("0")) {
//System.out.println("丢弃");
return false;
}
}
String answer = getAnswer(topic);
if ("-".equals(answer.charAt(0) + "") || answer.length() > 5)
return false;
return true;
}
3.toPoLand方法:
- 将中缀表达式转化为后缀表达式(利于答案的计算)
- 优点:转换后不用考虑运算符的优先次序,从左到右依次计算即可
public static String toPoLand(String topic) {
ArrayList<String> nums = new ArrayList<>();
LinkedList<String> symbols = new LinkedList<>();
String[] split = topic.split(" ");
String regex = "^\\d+(/\\d+)?$";
Pattern p = Pattern.compile(regex);
for (int i = 0; i < split.length; i++) {
if (split[i].matches(regex)) {
nums.add(split[i]);
} else {
if (symbols.size() == 0 || "(".equals(split[i])) {
symbols.addFirst(split[i]);
} else if (")".equals(split[i])) {
String s = symbols.removeFirst();
nums.add(s);
symbols.removeFirst();
} else {
String com = symbols.getFirst();
if ("(".equals(com))
symbols.addFirst(split[i]);
else if ("+".equals(split[i]) || "-".equals(split[i])) {
while (symbols.size() != 0) {
com = symbols.removeFirst();
nums.add(com);
}
symbols.addFirst(split[i]);
} else {
if ("*".equals(com) || "÷".equals(com)) {
com = symbols.removeFirst();
nums.add(com);
}
symbols.addFirst(split[i]);
}
}
}
}
while (symbols.size() != 0) {
String com = symbols.removeFirst();
nums.add(com);
}
StringJoiner sj = new StringJoiner(" ");
for (String num : nums) {
sj.add(num);
}
return sj.toString();
}
4.getAnswer方法:计算后缀表达式的答案
public static String getAnswer(String topic) {
double result = 0;
//调用转化方法
topic = toPoLand(topic);
LinkedList<String> sum = new LinkedList<>();
String[] split = topic.split(" ");
String fractionPattern = "^\\d+/\\d+$";
for (int i = 0; i < split.length; i++) {
if (split[i].equals("+") || split[i].equals("-") || split[i].equals("*") || split[i].equals("÷")) {
String str1 = sum.removeFirst();
String str2 = sum.removeFirst();
double num1, num2;
if (Pattern.matches(fractionPattern, str1)) {
String[] split1 = str1.split("/");
num1 = Double.parseDouble(split1[0]) / Double.parseDouble(split1[1]);
} else {
num1 = Double.parseDouble(str1);
}
if (Pattern.matches(fractionPattern, str2)) {
String[] split1 = str2.split("/");
num2 = Double.parseDouble(split1[0]) / Double.parseDouble(split1[1]);
} else {
num2 = Double.parseDouble(str2);
}
switch (split[i]) {
case "+": {
sum.addFirst(String.valueOf(num1 + num2));
break;
}
case "-": {
sum.addFirst(String.valueOf(num2 - num1));
break;
}
case "*": {
sum.addFirst(String.valueOf(num2 * num1));
break;
}
case "÷": {
sum.addFirst(String.valueOf(num2 / num1));
break;
}
}
} else {
sum.addFirst(split[i]);
}
}
result = Double.parseDouble(sum.removeFirst());
//小数转分数
String str = DecimalToFranctionUtil.convertDecimalToFraction(result);
return str;
}
5.allTopic方法:多次调用createTopic和getAnswer方法
public static void allTopics(int n, int r) throws IOException {
ArrayList<String> topics = new ArrayList<>();
// for (int i = 0; i < n; i++) {
//
// topics.add(createTopic(r));
// }
//计算每道题目的答案并存入list
ArrayList<String> answers = new ArrayList<>();
//调用计算答案的方法
for (int i = 0; i < n; i++) {
String topic = createTopic(r);
answers.add((i + 1) + ". " + getAnswer(topic));
topics.add((i + 1) + ". e = " + topic);
}
//将题目存入文件Exercises.txt
FileIOUtil.topicToFile(topics);
//将答案写入文件answers.txt
FileIOUtil.answerToFile(answers);
}
6.checkTopicAndAnswer方法:检查题目对应的答案是否正确
/*
判断文件的题目答案是否正确,并存入Grade.txt
Correct: 5 (1, 3, 5, 7, 9)
Wrong: 5 (2, 4, 6, 8, 10)
*/
public static void checkTopicAndAnswer(String exercisefile, String answerfile) throws IOException {
int correct = 0, wrong = 0;
ArrayList<Integer> cor = new ArrayList<>();
ArrayList<Integer> wro = new ArrayList<>();
//读取文件
ArrayList<String> topics = FileIOUtil.readFile(exercisefile);
ArrayList<String> answers = FileIOUtil.readFile(answerfile);
for (int i = 0; i < topics.size(); i++) {
if (getAnswer(topics.get(i).split("= ")[1]).equals(answers.get(i).split(". ")[1])) {
correct++;
cor.add(i + 1);
} else {
wrong++;
wro.add(i + 1);
}
ArrayList<String> list = new ArrayList<>();
list.add("Correct: " + correct + cor.toString());
list.add("Wrong: " + wrong + wro.toString());
FileIOUtil.gradeToFile(list);
}
}
1.2 DecimalToFranctionUtil工具类(小数转换为真分数的类)
包含的方法:
1.Fraction类:存放分母和分子
class Fraction {
private BigInteger numerator;
private BigInteger denominator;
public Fraction(BigInteger numerator, BigInteger denominator) {
this.numerator = numerator;
this.denominator = denominator;
}
public BigInteger getNumerator() {
return numerator;
}
public BigInteger getDenominator() {
return denominator;
}
}
2.convertDecimalToFraction方法:
public static String convertDecimalToFraction(double decimal) {
try {
// 将输入的double值转换为字符串
String str = String.valueOf(decimal);
// 使用BigDecimal来处理浮点数精度问题
BigDecimal bd = new BigDecimal(str);
// 获取小数的位数
int scale = bd.scale();
// 计算分母,比如0.75的分母为100,0.125的分母为1000
BigDecimal denominator = BigDecimal.TEN.pow(scale);
// 将小数转化为分数
BigDecimal numerator = bd.multiply(denominator);
// 使用BigInteger来处理分子和分母
BigInteger numeratorInt = numerator.toBigInteger();
BigInteger denominatorInt = denominator.toBigInteger();
// 使用最大公约数来简化分数
BigInteger gcd = numeratorInt.gcd(denominatorInt);
numeratorInt = numeratorInt.divide(gcd);
denominatorInt = denominatorInt.divide(gcd);
Fraction fraction = new Fraction(numeratorInt, denominatorInt);
str = fraction.getNumerator() + "/" + fraction.getDenominator();
if ("0".equals(String.valueOf(fraction.getNumerator())))
return "0";
if (fraction.getNumerator().equals(fraction.getDenominator()))
return "1";
if ("1".equals(String.valueOf(fraction.getDenominator())))
return String.valueOf(fraction.getNumerator());
// 假分数转带分数
long num1 = Long.parseLong(String.valueOf(fraction.getNumerator()));
long num2 = Long.parseLong(String.valueOf(fraction.getDenominator()));
long num;
if (num1 > num2) {
num = (num1 - (num1 % num2)) / num2;
str = num + "'" + (num1 % num2) + "/" + num2;
}
return str;
} catch (NumberFormatException e) {
// 处理输入格式错误的情况
return "Error: Invalid input. Please enter a valid number.";
} catch (ArithmeticException e) {
// 处理除以零等算术异常
return "Error: Division by zero or other arithmetic error";
} catch (Exception e) {
// 处理其他异常
return "Error: An unexpected error occurred";
}
}
程序运行结果
生成题目和答案:
文件:
判断题目和答案:
文件:
Grade.txt:
性能分析
遥测
实时内存-所有对象
CPU调用树
单元测试
FilesIOUtilTest
CountUtilTest
DecimalToFrantionUtilTest
测试代码覆盖率
分工合作
陈泽瀚:负责运行代码的编写和博客园编写
林桂旭:负责测试代码编写和代码提交以及性能测试
项目小结
- 结对项目中需要两人及时沟通,完成不同的分工任务。
- 写完运行代码,另一个同伴写测试代码可以发现一些自己发现不到的问题。
- 当工作中遇到困难时,要及时寻求解决方案,可以向其他成员寻求帮助,这样有助于提升效率。