【作业3】
黄鹏翔 3123004229 | 黄皓维 3123004228 |
---|---|
仓库地址 | 地址 |
这个作业的要求 | 结对项目 |
这个作业的目标 | 了解双人合作项目的方法,并完成此次项目 |
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 30 | 35 |
Estimate | 这个任务需要的时间 | 25 | 30 |
Development | 开发 | 200 | 240 |
Analysis | 需求分析 | 320 | 340 |
Design Spec | 生成设计文档 | 15 | 15 |
Design Review | 设计复审 | 15 | 15 |
Coding Standard | 代码规范 | 20 | 15 |
Design | 具体设计 | 20 | 20 |
Coding | 具体编码 | 100 | 110 |
Code Review | 代码复审 | 20 | 15 |
Test | 测试 | 200 | 230 |
Reporting | 报告 | 90 | 100 |
Test Repor | 测试报告 | 30 | 30 |
Size Measurement | 计算工作量 | 30 | 20 |
Postmortem & Process Improvement Plan | 事后总结,提出过程改进计划 | 20 | 20 |
合计 | 1135 | 1235 |
1.项目结构
src/main/java/com/test/
├── model/ // 基础数据模型
│ └── Fraction.java // 分数类,处理分数运算
│
├── expression/ // 表达式相关
│ ├── Expression.java // 表达式类
│ │
│ ├── node/ // 表达式节点
│ │ ├── ExpressionNode.java // 节点接口
│ │ ├── NumberNode.java // 数字节点
│ │ ├── OperatorNode/ // 运算符节点
│ │ │ ├── AddNode.java // 加法节点
│ │ │ ├── SubtractNode.java // 减法节点
│ │ │ ├── MultiplyNode.java // 乘法节点
│ │ │ └── DivideNode.java // 除法节点
│ │
│ └── parser/
│ └── ExpressionParser.java // 表达式解析器
│
├── service/ // 业务逻辑服务
│ ├── Generator.java // 题目生成器
│ └── Checker.java // 答案检查器
│
└── Main.java // 程序入口类
1.1核心类关系
classDiagram
Main --> Generator: uses
Main --> Checker: uses
Generator --> Expression: creates
Checker --> ExpressionParser: uses
ExpressionParser --> Expression: creates
Expression --> ExpressionNode: contains
NumberNode ..|> ExpressionNode: implements
AddNode ..|> ExpressionNode: implements
SubtractNode ..|> ExpressionNode: implements
MultiplyNode ..|> ExpressionNode: implements
DivideNode ..|> ExpressionNode: implements
ExpressionNode --> Fraction: uses
1.2工作流程
a.生成题目流程
Main -> Generator -> Expression -> ExpressionNode -> Fraction
b.答案检查流程
Main -> Checker -> ExpressionParser -> Expression -> Fraction
1.3流程图
main流程图
generator流程图
ExpressionParser流程图
Checker流程图
2.效能分析
CPU时间
内存分配
该程序中主要消耗占大头的有:
a.题目生成器中的查重=》主要消耗是重复生成和验证是否有重复的过程
b.表达式的解析
改进思路
对算法进行改进,通过优化查重的算法,减少该方法的消耗;改进表达式的解析过程;在内存管理方面,尝试对对象池进行管理,并在完成某些过程后及时释放资源
3.代码说明
3.1表达式解析器
public class ExpressionParser {
// 解析表达式字符串为表达式树
public static Expression parse(String input) throws ParseException {
// 去除空格,简化处理
input = input.replaceAll("\\s+", "");
// 递归构建表达式树
ExpressionNode root = parseNode(input);
return new Expression(root);
}
private static ExpressionNode parseNode(String input) throws ParseException {
// 处理括号
if (input.startsWith("(") && input.endsWith(")")) {
input = input.substring(1, input.length() - 1);
}
// 查找最后一个运算符(优先级最低)
int opIndex = findLastOperator(input);
if (opIndex == -1) {
// 没有运算符,解析数字或分数
return new NumberNode(parseFraction(input));
} else {
// 根据运算符分割并递归处理左右子表达式
String left = input.substring(0, opIndex);
String right = input.substring(opIndex + 1);
char operator = input.charAt(opIndex);
// 创建对应的运算符节点
return createOperatorNode(operator,
parseNode(left), parseNode(right));
}
}
// 查找最后一个运算符,考虑括号嵌套
private static int findLastOperator(String input) {
int bracketCount = 0;
int lastAddSub = -1; // 最后的加减号位置
int lastMulDiv = -1; // 最后的乘除号位置
for (int i = 0; i < input.length(); i++) {
char c = input.charAt(i);
if (c == '(') bracketCount++;
else if (c == ')') bracketCount--;
else if (bracketCount == 0) { // 只在没有括号时处理运算符
if (c == '+' || c == '-') lastAddSub = i;
else if (c == '×' || c == '÷') lastMulDiv = i;
}
}
// 优先返回加减号位置,体现运算符优先级
return lastAddSub >= 0 ? lastAddSub : lastMulDiv;
}
}
思路说明:
-
采用递归下降法构建表达式树
-
通过运算符优先级控制树的结构
-
使用括号计数处理嵌套表达式
3.2分数运算
public Fraction(int numerator, int denominator) {
if (denominator == 0) {
throw new IllegalArgumentException("分母不能为0");
}
// 统一处理负号
if (denominator < 0) {
numerator = -numerator;
denominator = -denominator;
}
// 化简分数
int gcd = gcd(Math.abs(numerator), denominator);
this.numerator = numerator / gcd;
this.denominator = denominator / gcd;
}
// 加法运算
public Fraction add(Fraction other) {
// 通分相加
int newNumerator = this.numerator * other.denominator +
other.numerator * this.denominator;
int newDenominator = this.denominator * other.denominator;
return new Fraction(newNumerator, newDenominator);
}
// 最大公约数(辗转相除法)
private static int gcd(int a, int b) {
while (b != 0) {
int temp = b;
b = a % b;
a = temp;
}
return a;
}
// 转换为字符串表示
@Override
public String toString() {
if (denominator == 1) {
return String.valueOf(numerator);
}
// 处理带分数形式
if (Math.abs(numerator) > denominator) {
int whole = numerator / denominator;
int remain = Math.abs(numerator % denominator);
return remain == 0 ? String.valueOf(whole)
: whole + "'" + remain + "/" + denominator;
}
return numerator + "/" + denominator;
}
}
思路说明:
-
保持分数始终处于最简形式
-
统一处理负号位置
-
支持带分数表示法
-
使用辗转相除法求最大公约数
3.3题目生成器
public List<String> generateExercises(int count) {
List<String> exercises = new ArrayList<>();
Set<String> uniqueExpressions = new HashSet<>();
while (exercises.size() < count) {
Expression expr = generateExpression();
String exprStr = expr.toString();
// 检查是否重复且结果合法
if (!uniqueExpressions.contains(exprStr) &&
isValidResult(expr.evaluate())) {
exercises.add(exprStr);
uniqueExpressions.add(exprStr);
}
}
return exercises;
}
private Expression generateExpression() {
// 生成运算符数量(1-3个)
int operatorCount = random.nextInt(3) + 1;
// 构建表达式树
ExpressionNode root = generateNode(operatorCount);
return new Expression(root);
}
private ExpressionNode generateNode(int depth) {
if (depth == 0) {
// 生成叶子节点(数字)
return generateNumberNode();
}
// 生成运算符节点
char[] operators = {'+', '-', '×', '÷'};
char operator = operators[random.nextInt(operators.length)];
return createOperatorNode(operator,
generateNode(depth - 1),
generateNumberNode());
}
// 检查结果是否合法(正数且不超过限制)
private boolean isValidResult(Fraction result) {
return result.getNumerator() > 0 &&
result.getNumerator() <= MAX_NUMBER &&
result.getDenominator() <= MAX_NUMBER;
}
}
思路说明:
-
随机生成表达式树
-
控制运算符数量和数值范围
-
确保结果为正数且在合理范围内
-
避免重复题目
4.测试代码
答案解析\检查器
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
import java.io.IOException;
import java.util.List;
public class CheckerTest {
@Test
public void testCheck() throws IOException {
// 定义测试文件路径
String exerciseFile = "exercisefile.txt";
String answerFile = "answerfile.txt";
// 调用 check 方法
Checker.check(exerciseFile, answerFile);
// 读取生成的 Grade.txt 文件并验证内容
List<String> lines = java.nio.file.Files.readAllLines(java.nio.file.Paths.get("Grade.txt"));
assertFalse(lines.isEmpty(), "Grade.txt 文件不应为空");
// 验证 Correct 和 Wrong 的数量是否正确
String correctLine = lines.get(0);
String wrongLine = lines.get(1);
assertTrue(correctLine.startsWith("Correct: "), "第一行应包含 Correct 信息");
assertTrue(wrongLine.startsWith("Wrong: "), "第二行应包含 Wrong 信息");
// 打印结果以供检查
System.out.println("Correct Line: " + correctLine);
System.out.println("Wrong Line: " + wrongLine);
}
}
表达式解析器测试
import org.junit.Test;
import static org.junit.Assert.*;
import java.text.ParseException;
//测试 ExpressionParser(表达式解析器)是否能够正确解析数学表达式
public class ExpressionParserTest {
//测试整数解析
@Test
public void testParseInteger() throws ParseException {
Expression expr = ExpressionParser.parse("5");
assertEquals("5", expr.evaluate().toString());
}
//测试分数解析
@Test
public void testParseFraction() throws ParseException {
Expression expr = ExpressionParser.parse("3/4");
assertEquals("3/4", expr.evaluate().toString());
}
//测试带分数解析
@Test
public void testParseMixedFraction() throws ParseException {
Expression expr = ExpressionParser.parse("1'1/2");
assertEquals("1'1/2", expr.evaluate().toString());
}
}
表达式计算测试
import org.junit.Test;
import static org.junit.Assert.*;
//测试 Expression(表达式)计算是否正确
public class ExpressionTest {
//测试简单的加法计算
@Test
public void testSimpleAddition() {
Expression expr = new Expression(new AddNode(new NumberNode(new Fraction(1, 2)), new NumberNode(new Fraction(1, 3))));
assertEquals("5/6", expr.evaluate().toString());
}
//测试简单的减法计算
@Test
public void testSimpleSubtraction() {
Expression expr = new Expression(new SubtractNode(new NumberNode(new Fraction(3, 4)), new NumberNode(new Fraction(1, 4))));
assertEquals("1/2", expr.evaluate().toString());
}
//测试乘法计算
@Test
public void testMultiplication() {
Expression expr = new Expression(new MultiplyNode(new NumberNode(new Fraction(2, 3)), new NumberNode(new Fraction(3, 4))));
assertEquals("1/2", expr.evaluate().toString());
}
//测试除法计算
@Test
public void testDivision() {
Expression expr = new Expression(new DivideNode(new NumberNode(new Fraction(3, 4)), new NumberNode(new Fraction(2, 5))));
assertEquals("1'7/8", expr.evaluate().toString());
}
//测试将加法节点转换为字符串
@Test
public void testToString1() {
Expression expr = new Expression(new AddNode(new NumberNode(new Fraction(1, 2)), new NumberNode(new Fraction(1, 3))));
assertEquals("1/2 + 1/3", expr.toString());
}
//测试将减法节点转换为字符串
@Test
public void testToString2() {
Expression expr = new Expression(new SubtractNode(new NumberNode(new Fraction(1, 2)), new NumberNode(new Fraction(1, 3))));
assertEquals("1/2 - 1/3", expr.toString());
}
//测试将乘法节点转换为字符串
@Test
public void testToString3() {
Expression expr = new Expression(new MultiplyNode(new NumberNode(new Fraction(1, 2)), new NumberNode(new Fraction(1, 3))));
assertEquals("1/2 × 1/3", expr.toString());
}
//测试将除法节点转换为字符串
@Test
public void testToString4() {
Expression expr = new Expression(new DivideNode(new NumberNode(new Fraction(1, 2)), new NumberNode(new Fraction(1, 3))));
assertEquals("1/2 ÷ 1/3", expr.toString());
}
}
分数类测试
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.*;
//测试 Fraction(分数)类的功能,包括四则运算、约分、负数处理等。
public class FractionTest {
private Fraction f1, f2, f3, f4;
//初始化测试数据
@Before
public void setUp() {
f1 = new Fraction(1, 2); // 1/2
f2 = new Fraction(1, 3); // 1/3
f3 = new Fraction(-2, 4); // -1/2 (约分)
f4 = new Fraction(4, 8); // 1/2 (约分)
}
//测试分数是否能够正确约分
@Test
public void testFractionReduction() {
assertEquals("1/2", f4.toString()); // 4/8 应该被约分成 1/2
}
//测试负数分数的正确性
@Test
public void testNegativeFraction() {
assertEquals("-1/2", f3.toString()); // -2/4 应该变成 -1/2
}
// 测试分数加法
@Test
public void testAddition() {
assertEquals("5/6", f1.add(f2).toString()); // 1/2 + 1/3 = 5/6
}
//测试分数减法
@Test
public void testSubtraction() {
assertEquals("1/6", f1.subtract(f2).toString()); // 1/2 - 1/3 = 1/6
}
//测试分数乘法
@Test
public void testMultiplication() {
assertEquals("1/6", f1.multiply(f2).toString()); // 1/2 * 1/3 = 1/6
}
//测试分数除法
@Test
public void testDivision() {
assertEquals("1'1/2", f1.divide(f2).toString()); // 1/2 ÷ 1/3 = 3/2
}
// 测试两个相等的分数是否被认为相等
@Test
public void testEquality() {
assertEquals(new Fraction(1, 2), f4); // 1/2 应该等于 4/8
}
}
表达式生成测试
import org.junit.Test;
import static org.junit.Assert.*;
public class GeneratorTest {
// 测试生成的表达式不超过最大值
@Test
public void testGeneratedExpressionWithinRange() {
int maxValue = 10;
Generator generator = new Generator(maxValue);
Expression expr = generator.generate();
Fraction result = expr.evaluate();
String str = result.toString();
// 解析带分数、假分数和整数三种情况
int numerator;
int denominator;
if (str.contains("'")) { // 带分数格式,如 "2'1/2"
String[] parts = str.split("'");
int whole = Integer.parseInt(parts[0]);
String[] fractionParts = parts[1].split("/");
int remNumerator = Integer.parseInt(fractionParts[0]);
denominator = Integer.parseInt(fractionParts[1]);
numerator = whole * denominator + remNumerator;
} else if (str.contains("/")) { // 假分数格式,如 "3/2"
String[] fractionParts = str.split("/");
numerator = Integer.parseInt(fractionParts[0]);
denominator = Integer.parseInt(fractionParts[1]);
} else { // 整数格式,如 "5"
numerator = Integer.parseInt(str);
denominator = 1;
}
int maxAllowed = maxValue * denominator;
assertTrue("分子超过最大值" + maxAllowed + ",当前值为:" + numerator,
numerator <= maxAllowed);
}
// 测试生成的表达式不为空
@Test
public void testGeneratedExpressionNotNull() {
Generator generator = new Generator(10);
Expression expr = generator.generate();
assertNotNull(expr);
}
}
以上测试代码在功能测试中是已经测试了基本运算的正确性,表达式的处理,分数的化简;边界测试中的最大最小值,特殊的分数格式;异常测试中的非法输入,文件操作异常。因此可以确定该程序能够正确运行
5.项目小结
本次项目的完成,我们是采用结对编程的方式进行开发。在合作中我们学到如何高效合作,高效沟通,快速分工,提高效率。通过先搭建起项目框架的,然后层层优化性能,最终得到此程序