黄鹏翔 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 {@Testpublic 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 {//测试整数解析@Testpublic void testParseInteger() throws ParseException {Expression expr = ExpressionParser.parse("5");assertEquals("5", expr.evaluate().toString());}//测试分数解析@Testpublic void testParseFraction() throws ParseException {Expression expr = ExpressionParser.parse("3/4");assertEquals("3/4", expr.evaluate().toString());}//测试带分数解析@Testpublic 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 {//测试简单的加法计算@Testpublic 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());}//测试简单的减法计算@Testpublic 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());}//测试乘法计算@Testpublic 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());}//测试除法计算@Testpublic 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());}//测试将加法节点转换为字符串@Testpublic 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());}//测试将减法节点转换为字符串@Testpublic 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());}//测试将乘法节点转换为字符串@Testpublic 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());}//测试将除法节点转换为字符串@Testpublic 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;//初始化测试数据@Beforepublic void setUp() {f1 = new Fraction(1, 2); // 1/2f2 = new Fraction(1, 3); // 1/3f3 = new Fraction(-2, 4); // -1/2 (约分)f4 = new Fraction(4, 8); // 1/2 (约分)}//测试分数是否能够正确约分@Testpublic void testFractionReduction() {assertEquals("1/2", f4.toString()); // 4/8 应该被约分成 1/2}//测试负数分数的正确性@Testpublic void testNegativeFraction() {assertEquals("-1/2", f3.toString()); // -2/4 应该变成 -1/2}// 测试分数加法@Testpublic void testAddition() {assertEquals("5/6", f1.add(f2).toString()); // 1/2 + 1/3 = 5/6}//测试分数减法@Testpublic void testSubtraction() {assertEquals("1/6", f1.subtract(f2).toString()); // 1/2 - 1/3 = 1/6}//测试分数乘法@Testpublic void testMultiplication() {assertEquals("1/6", f1.multiply(f2).toString()); // 1/2 * 1/3 = 1/6}//测试分数除法@Testpublic void testDivision() {assertEquals("1'1/2", f1.divide(f2).toString()); // 1/2 ÷ 1/3 = 3/2}// 测试两个相等的分数是否被认为相等@Testpublic void testEquality() {assertEquals(new Fraction(1, 2), f4); // 1/2 应该等于 4/8}
}
表达式生成测试
import org.junit.Test;
import static org.junit.Assert.*;public class GeneratorTest {// 测试生成的表达式不超过最大值@Testpublic 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);}// 测试生成的表达式不为空@Testpublic void testGeneratedExpressionNotNull() {Generator generator = new Generator(10);Expression expr = generator.generate();assertNotNull(expr);}
}
以上测试代码在功能测试中是已经测试了基本运算的正确性,表达式的处理,分数的化简;边界测试中的最大最小值,特殊的分数格式;异常测试中的非法输入,文件操作异常。因此可以确定该程序能够正确运行
5.项目小结
本次项目的完成,我们是采用结对编程的方式进行开发。在合作中我们学到如何高效合作,高效沟通,快速分工,提高效率。通过先搭建起项目框架的,然后层层优化性能,最终得到此程序