【作业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.项目小结

本次项目的完成,我们是采用结对编程的方式进行开发。在合作中我们学到如何高效合作,高效沟通,快速分工,提高效率。通过先搭建起项目框架的,然后层层优化性能,最终得到此程序

posted @ 2025-03-21 13:59  hhhhhw-  阅读(26)  评论(0)    收藏  举报