四则运算器
钟宝骏 3123004249
| 这个作业属于哪个课程 | 班级的链接 |
| 这个作业要求在哪里 | 作业要求 |
| 这个作业的目标 | 提高工程能力和算法能力 |
github: https://github.com/Tsukilc/cal
1. PSP 表格
| PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
|---|---|---|---|
| Planning | 计划 | 5 | 5 |
| · Estimate | · 估计这个任务需要多少时间 | 220 | 200 |
| Development | 开发 | 160 | 160 |
| · Analysis | · 需求分析 (包括学习新技术) | 5 | 5 |
| · Design Spec | · 生成设计文档 | 5 | 5 |
| · Design Review | · 设计复审 (和同事审核设计文档) | 5 | 5 |
| · Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 10 | 10 |
| · Design | · 具体设计 | 5 | 5 |
| · Coding | · 具体编码 | 100 | 100 |
| · Code Review | · 代码复审 | 10 | 10 |
| · Test | · 测试(自我测试,修改代码,提交修改) | 20 | 20 |
| Reporting | 报告 | 40 | 40 |
| · Test Report | · 测试报告 | 20 | 20 |
| · Size Measurement | · 计算工作量 | 5 | 5 |
| · Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 15 | 15 |
| 合计 | 220 | 200 |
2. 效能分析
2.1 改进程序性能花费的时间
20分钟
2.2 改进思路
首先,我使用了性能分析工具,如JProfiler和VisualVM,识别了程序中的关键瓶颈。通过这些工具,我们能够明确哪些模块和操作对性能影响最大。
之后我从字符串处理的细节优化了生成算术题的代码
并且优化了文件传输的代码
2.3 性能分析图
火焰图

方法耗时图

时间线图

2.4 程序中消耗最大的函数
是生成问题和答案的函数!因为涉及到了很多字符串处理和分数操作,并且还要给问题算出最简的分数答案,耗时比较多
3. 设计实现过程



4. 代码说明
4.1 关键代码展示
- 生成表达式
通过控制运算符数量、操作数范围和括号的使用,能够构建出多种多样的复杂表达式。它保证了运算符和操作数之间的合理性,例如避免负数结果和除零错误,同时还根据概率随机在子表达式中添加括号,从而提升了表达式的多样性和复杂度。
这里我想到了一个方法,先把字符串的空格全部清掉,方便字符串处理
之后再全部加回去,方便阅读
private static final int MAX_OPERATORS = 3; // 最大运算符数量
private static final double BRACKET_PROBABILITY = 0.7; // 括号出现的概率
// 生成表达式的方法
public static String generateExpression(int numOperations, int rangeLimit) {
if (numOperations > MAX_OPERATORS) {
throw new IllegalArgumentException("运算符数量不能超过 " + MAX_OPERATORS);
}
StringBuilder expression = new StringBuilder();
ArrayList<String> operators = new ArrayList<>(Arrays.asList("+", "-", "*", "/"));
// 生成第一个操作数
int operand = random.nextInt(rangeLimit);
expression.append(operand);
// 生成后续的操作数和运算符
for (int i = 0; i < numOperations; i++) {
// 随机选择运算符
String operator = operators.get(random.nextInt(operators.size()));
// 随机选择下一个操作数
operand = random.nextInt(rangeLimit);
// 确保减法不会导致负数结果
if (operator.equals("-") && operand > 0) {
operand = random.nextInt(rangeLimit - 1);
}
// 确保除法结果为真分数
if (operator.equals("/")) {
int denominator = random.nextInt(rangeLimit - 1) + 1; // 避免除以零
expression.append("/").append(denominator);
} else {
// 添加运算符和操作数到表达式
expression.append(operator).append(operand);
}
// 随机决定是否在当前子表达式周围添加括号
if (random.nextDouble() < BRACKET_PROBABILITY) {
int insertPos = random.nextInt(expression.length());
if (insertPos + 1 >= expression.length()){
continue;
}
char nextChar = expression.charAt(insertPos + 1);
if ( nextChar == '*' || nextChar == '/' || isDigit(nextChar))
continue;
expression.insert(insertPos, "(");
expression.append(")");
}
}
// 确保括号正确匹配
String result = expression.toString().replaceAll("\\(\\s*\\*", "\\(\\*").replaceAll("\\(\\s*/", "\\(/");
// 给result表达式加上空格
result = result.replaceAll("\\+", " + ").replaceAll("-", " - ").replaceAll("\\*", " * ").replaceAll("/", " / ");
return result;
}
private static boolean isDigit(char c) {
return c >= '0' && c <= '9';
}
- 表达式计算器
通过栈结构巧妙地进行运算符优先级管理和括号匹配,确保计算顺序正确。类中的分数以最简形式返回,且能有效处理各种复杂的表达式。
在实现上,类提供了以下特点:
- 表达式有效性验证:在计算之前,首先检查表达式是否有效,确保括号匹配且没有非法字符。
- 栈式计算:利用栈管理分子、分母和操作符,确保操作符优先级和括号的正确处理。
- 分数计算:对加、减、乘、除四则运算进行精确的分数运算,避免浮点数误差。
- 分数简化:运算结果会自动简化为最简分数形式,确保输出结果简洁、精准。
public static String evaluateExpression(String expr) {
try {
// 去掉所有空格
expr = expr.replaceAll(" ", "");
// 验证表达式的有效性
if (!isValidExpression(expr)) {
throw new IllegalArgumentException("Invalid expression");
}
// 使用栈来处理表达式
Stack<BigInteger> numerators = new Stack<>(); // 分子栈
Stack<BigInteger> denominators = new Stack<>(); // 分母栈
Stack<Character> operators = new Stack<>(); // 操作符栈
int i = 0;
while (i < expr.length()) {
char c = expr.charAt(i);
if (isDigit(c)) {
// 解析数字
StringBuilder numBuilder = new StringBuilder();
while (i < expr.length() && isDigit(expr.charAt(i))) {
numBuilder.append(expr.charAt(i++));
}
BigInteger numerator = new BigInteger(numBuilder.toString());
BigInteger denominator = BigInteger.ONE;
// 处理分数形式:如果有 / 处理分母
if (i < expr.length() && expr.charAt(i) == '/') {
i++; // 跳过 '/'
numBuilder.setLength(0); // 清空数字构建器
while (i < expr.length() && isDigit(expr.charAt(i))) {
numBuilder.append(expr.charAt(i++));
}
denominator = new BigInteger(numBuilder.toString());
}
// 将解析出的数字和分数压栈
numerators.push(numerator);
denominators.push(denominator);
} else if (c == '(') {
// 处理左括号,直接压栈
operators.push(c);
i++;
} else if (c == ')') {
// 处理右括号,进行运算直到遇到左括号
while (operators.peek() != '(') {
performOperation(numerators, denominators, operators);
}
operators.pop(); // 弹出 '('
i++;
} else if (isOperator(c)) {
// 处理运算符
while (!operators.isEmpty() && precedence(c) <= precedence(operators.peek())) {
performOperation(numerators, denominators, operators);
}
operators.push(c);
i++;
}
}
// 执行所有剩余的运算
while (!operators.isEmpty()) {
performOperation(numerators, denominators, operators);
}
// 最终的结果是栈顶的分子和分母
BigInteger finalNumerator = numerators.pop();
BigInteger finalDenominator = denominators.pop();
// 简化分数
BigInteger gcd = finalNumerator.gcd(finalDenominator);
finalNumerator = finalNumerator.divide(gcd);
finalDenominator = finalDenominator.divide(gcd);
// 返回最简分数形式
if (finalDenominator.equals(BigInteger.ONE)) {
return finalNumerator.toString(); // 整数形式
} else {
return finalNumerator + "/" + finalDenominator;
}
} catch (Exception e) {
return "Error"; // 捕获错误
}
}
5. 单元测试
5.1 覆盖率
- 使用idea profile

5.2 十条测试细节
基础运算测试:
@Test
void testBasicOperations() {
assertEquals("3", Cal.evaluateExpression("1 + 2"));
assertEquals("1/2", Cal.evaluateExpression("3/4 - 1/4"));
assertEquals("3/4", Cal.evaluateExpression("1/2 * 3/2"));
assertEquals("2", Cal.evaluateExpression("4/3 / 2/3"));
}
说明:测试基本的加减乘除运算,确保程序能够正确处理这些操作。
分数约简测试:
@Test
void testSimplification() {
assertEquals("1/2", Cal.evaluateExpression("2/4"));
assertEquals("3", Cal.evaluateExpression("6/2"));
assertEquals("5/7", Cal.evaluateExpression("10/14"));
}
说明:测试分数的约简功能,确保程序能够正确简化分数。
错误处理测试:
@Test
void testErrorCases() {
assertEquals("Error", Cal.evaluateExpression("(1 + 2"));
assertEquals("Error", Cal.evaluateExpression("1 + 2)"));
assertEquals("Error", Cal.evaluateExpression("1 + a"));
}
说明:测试错误处理,确保程序能够正确识别并处理无效的表达式。
嵌套运算测试:
@Test
void testNestedOperations() {
assertEquals("9/16", Cal.evaluateExpression("(1/2 + (3/4 - 1/8)) * 1/2"));
assertEquals("1", Cal.evaluateExpression("(1)"));
}
说明:测试嵌套运算,确保程序能够正确处理带有括号的复杂表达式。
大数运算测试:
@Test
void testLargeNumbers() {
assertEquals("1000000000000", Cal.evaluateExpression("1000000000 * 1000"));
assertEquals("1", Cal.evaluateExpression("1000000000 / 1000000000"));
}
说明:测试大数运算,确保程序能够正确处理大数的计算。
零除测试:
@Test
void testDivisionByZero() {
assertEquals("Error", Cal.evaluateExpression("1 / 0"));
}
说明:测试除以零的情况,确保程序能够正确处理并返回错误。
负数运算测试:
@Test
void testNegativeNumbers() {
assertEquals("-1", Cal.evaluateExpression("1 - 2"));
assertEquals("-1/2", Cal.evaluateExpression("1/2 - 1"));
}
说明:测试负数运算,确保程序能够正确处理负数的计算。
多运算符测试
@Test
void testMultipleOperators() {
assertEquals("0", Cal.evaluateExpression("1 + 2 - 3"));
assertEquals("1", Cal.evaluateExpression("1 * 2 / 2"));
}
说明:测试多个运算符的情况,确保程序能够正确处理多个运算符的表达式。
空格处理测试:
@Test
void testWhitespaceHandling() {
assertEquals("3", Cal.evaluateExpression(" 1 + 2 "));
assertEquals("1/2", Cal.evaluateExpression(" 3 / 4 - 1 / 4 "));
}
说明:测试表达式中的空格处理,确保程序能够正确忽略空格。
复杂表达式测试:
@Test
void testComplexExpression() {
assertEquals("7/8", Cal.evaluateExpression("1/2 + 1/4 + 1/8"));
assertEquals("1/6", Cal.evaluateExpression("1/2 * 1/3"));
}
说明:测试复杂表达式,确保程序能够正确处理复杂的分数运算。
6. 最终效果
要求 1 2 5 7 8 9展示
java -jar E:\idea-code\cal\cal-1.0-SNAPSHOT.jar -n 10000 -r 100 -e Exercises.txt -a Answers.txt


要求3 4在代码有体现
// 确保除法结果为真分数
if (operator.equals("/")) {
int denominator = random.nextInt(rangeLimit - 1) + 1; // 避免除以零
expression.append("/").append(denominator);
} else {
// 添加运算符和操作数到表达式
expression.append(operator).append(operand);
}
7 反思总结
可惜写的太晚了。。。最后没有找到队友
一个人独立完成项目工作量还是比较大,并且缺少团队的反馈
如果有队友,可能可以更好的平衡任务
不过这次让我了解了java的性能分析和测试工具(上次用的Python),并且深入了解了逆波兰表达式和一些异常处理等,收获满满

浙公网安备 33010602011771号