这个作业属于哪个课程 | https://edu.cnblogs.com/campus/gdgy/CSGrade22-34 |
---|---|
这个作业要求在哪里 | https://edu.cnblogs.com/campus/gdgy/CSGrade22-34/homework/13230 |
这个作业的目标 | <结对合作完成项目,实现一个自动生成小学四则运算题目的命令行程序> |
成员1 | 唐立伟 3122005404 |
成员2 | 黄妍仪 3222004767 |
Github链接
https://github.com/tangliweiwww/Arithmetic
PSP2.1表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 50 | 60 |
· Estimate | · 估计这个任务需要多少时间 | 700 | 750 |
Development | 开发 | 450 | 530 |
· Analysis | · 需求分析 (包括学习新技术) | 50 | 55 |
· Design Spec | · 生成设计文档 | 25 | 25 |
· Design Review | · 设计复审 (和同事审核设计文档) | 25 | 30 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 20 | 35 |
· Design | · 具体设计 | 50 | 55 |
· Coding | · 具体编码 | 200 | 240 |
· Code Review | · 代码复审 | 40 | 40 |
· Test | · 测试(自我测试,修改代码,提交修改) | 40 | 50 |
Reporting | 报告 | 100 | 125 |
· Test Report | · 测试报告 | 40 | 45 |
· Size Measurement | · 计算工作量 | 40 | 55 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 20 | 25 |
合计 | 600 | 715 |
程序设计
类名 | 方法名 | 用途 |
---|---|---|
FractionCalculator | evaluateFractionExpression | 计算 |
parseFraction | 解析分数为BigFraction | |
isOperator | 判断符号 | |
precedence | 设定优先级 | |
applyOperator | 进行数学运算 | |
FractionChange | convertExpressionToMixedFractions | 将算式中的分式转换为带分数 |
convertMixedFractionsToImproperFractions | 将带分数转换为普通分数 | |
MathProblemGenerator | generateRandomProblem | 生成一个随机题目 |
generateOperand | 生成操作数 | |
evaluateOperand | 计算操作数的值 | |
isValidExpression | 检查计算结果是否符合要求 | |
WriteToFile_ProblemAndAnswers | Write | 生成答案和题目并写入文件 |
Judge | JudgeFile | 生成判题结果写入文件 |
执行流程
代码说明
MathProblemGenerator
用于生成题目,实现了一个数学题目生成器,能够生成带有分数的数学表达式。
生成随机题目:通过 generateUniqueProblems 方法生成指定数量的唯一数学题目,确保每个题目都是合法的。
表达式构造:使用 generateRandomProblem 方法随机生成操作数和运算符,支持加、减、乘、除等基本运算,并随机决定是否使用括号。
合法性检查:isValidExpression 方法使用第三方库解析生成的表达式,并验证其结果是否符合要求(如非负)。
分数和整数生成:generateOperand 方法随机生成整数或分数,确保题目的多样性。
结果评估:evaluateOperand 方法计算操作数的值,支持分数的计算。
public class MathProblemGenerator {
private static final Random RANDOM = new Random(); // 随机数生成器
// 生成唯一题目集合
public static Set<String> generateUniqueProblems(int numberOfProblems, int range) {
Set<String> problems = new LinkedHashSet<>(); // 使用LinkedHashSet保持插入顺序
while (problems.size() < numberOfProblems) {
String problem = generateRandomProblem(range); // 生成随机题目
if (isValidExpression(problem)) { // 检查题目是否合法
problems.add(FractionChange.convertExpressionToMixedFractions(problem)); // 转换为混合分数并添加到集合
}
}
return problems; // 返回生成的题目集合
}
// 生成一个随机题目
private static String generateRandomProblem(int range) {
String[] operators = {"+", "-", "*", "/"}; // 可用运算符
StringBuilder problem = new StringBuilder(); // 存储生成的题目
Stack<Boolean> parenthesesStack = new Stack<>(); // 用于管理括号的栈
// 生成两个操作数和一个运算符
String operand1 = generateOperand(range); // 生成第一个操作数
String operand2 = generateOperand(range); // 生成第二个操作数
String operator = operators[RANDOM.nextInt(operators.length)]; // 随机选择一个运算符
// 确保减法时前面的数大于等于后面的数
if ("-".equals(operator)) {
while (evaluateOperand(operand1) < evaluateOperand(operand2)) {
operand1 = generateOperand(range); // 重新生成第一个操作数
operand2 = generateOperand(range); // 重新生成第二个操作数
}
}
// 随机决定是否生成括号
if (RANDOM.nextBoolean()) {
problem.append("(");
parenthesesStack.push(true); // 打开括号
}
// 组装题目
problem.append(operand1)
.append(" ")
.append(operator)
.append(" ")
.append(operand2);
// 随机决定是否生成第二部分的表达式
if (RANDOM.nextBoolean()) {
operator = operators[RANDOM.nextInt(operators.length)];
String operand3 = generateOperand(range); // 生成第三个操作数
String operand4 = generateOperand(range); // 生成第四个操作数
// 确保减法时前面的数大于等于后面的数
if ("-".equals(operator)) {
while (evaluateOperand(operand3) < evaluateOperand(operand4)) {
operand3 = generateOperand(range);
operand4 = generateOperand(range);
}
}
problem.append(" ")
.append(operator)
.append(" ");
// 随机决定是否给第二部分加括号
if (RANDOM.nextBoolean()) {
problem.append("(");
parenthesesStack.push(true); // 打开括号
}
problem.append(operand3)
.append(" ")
.append(operators[RANDOM.nextInt(operators.length)]) // 随机选择运算符
.append(" ")
.append(operand4);
// 关闭括号
while (!parenthesesStack.isEmpty()) {
problem.append(")");
parenthesesStack.pop(); // 关闭括号
}
}
// 如果还有未关闭的括号,最后关闭它
while (!parenthesesStack.isEmpty()) {
problem.append(")");
parenthesesStack.pop(); // 关闭剩余的括号
}
return problem.toString(); // 返回生成的题目字符串
}
// 生成操作数
private static String generateOperand(int range) {
if (RANDOM.nextBoolean()) {
// 生成整数
return String.valueOf(RANDOM.nextInt(range) + 1);
} else {
// 生成分数
int numerator = RANDOM.nextInt(range) + 1; // 随机生成分子
int denominator = RANDOM.nextInt(range) + 1; // 随机生成分母
return numerator + "/" + denominator; // 返回分数形式
}
}
// 计算操作数的值
private static double evaluateOperand(String operand) {
if (operand.contains("/")) {
String[] parts = operand.split("/"); // 分割分数
return Double.parseDouble(parts[0]) / Double.parseDouble(parts[1]); // 计算并返回小数值
} else {
return Double.parseDouble(operand); // 返回整数值
}
}
// 检查计算结果是否符合要求
private static boolean isValidExpression(String expression) {
// 创建表达式解析器
Expression e = new ExpressionBuilder(expression).build();
// 计算并返回结果
double result = e.evaluate();
// 如果结果小于0,则不合法
if (result < 0) {
return false;
}
return true; // 合法表达式
}
}
FractionChange
这段代码主要实现了分数和带分数之间的转换功能。主要功能包括:
分数转换为带分数:使用正则表达式查找算式中的普通分数,并将其转换为带分数形式。
带分数转换为普通分数:提供功能将带分数(如 2'1/3)转换为不合适分数(如 7/3)。
分数解析与化简:解析输入的分数字符串,将其分解为分子和分母,并通过计算最大公约数(GCD)对分数进行化简。
public class FractionChange {
// 将算式中的分式转换为带分数
public static String convertExpressionToMixedFractions(String expression) {
// 正则表达式匹配分数
Pattern fractionPattern = Pattern.compile("(\\d+/\\d+)");
Matcher matcher = fractionPattern.matcher(expression);
StringBuffer result = new StringBuffer();
// 查找并转换分数
while (matcher.find()) {
String fraction = matcher.group(1);
int[] parsedFraction = parseFraction(fraction); // 解析分数
String mixedFraction = convertToMixedFraction(parsedFraction[0], parsedFraction[1]); // 转换为带分数
matcher.appendReplacement(result, mixedFraction); // 替换为带分数
}
matcher.appendTail(result); // 添加剩余部分
return result.toString(); // 返回转换后的表达式
}
// 将字符串形式的分数解析为整数数组,返回[分子, 分母]
public static int[] parseFraction(String fraction) {
String[] parts = fraction.split("/"); // 分割字符串
int numerator = Integer.parseInt(parts[0]); // 分子
int denominator = Integer.parseInt(parts[1]); // 分母
return new int[] {numerator, denominator};
}
// 求最大公约数(GCD)
public static int gcd(int a, int b) {
if (b == 0) {
return a; // 递归终止条件
}
return gcd(b, a % b); // 计算GCD
}
// 将分数转换为带分数形式并化简
public static String convertToMixedFraction(int numerator, int denominator) {
// 化简分数部分
int gcd = gcd(numerator, denominator); // 计算GCD
numerator /= gcd; // 简化分子
denominator /= gcd; // 简化分母
// 如果分子小于分母,直接返回简化后的普通分数
if (numerator < denominator) {
return numerator + "/" + denominator;
}
// 计算整数部分和余数
int wholeNumber = numerator / denominator;
int remainder = numerator % denominator;
// 如果没有余数,返回整数
if (remainder == 0) {
return Integer.toString(wholeNumber);
}
// 否则返回带分数形式(化简后的结果)
return wholeNumber + "'" + remainder + "/" + denominator;
}
// 将带分数转换为普通分数
public static String convertMixedFractionsToImproperFractions(String expression) {
// 正则表达式匹配带分数
Pattern mixedFractionPattern = Pattern.compile("(\\d+)'(\\d+)/(\\d+)");
Matcher matcher = mixedFractionPattern.matcher(expression);
StringBuffer result = new StringBuffer();
// 查找并转换带分数
while (matcher.find()) {
String mixedFraction = matcher.group();
String improperFraction = convertMixedFractionToImproperFraction(mixedFraction); // 转换为普通分数
System.out.println("Converting: " + mixedFraction + " to " + improperFraction); // 调试输出
matcher.appendReplacement(result, improperFraction); // 替换为普通分数
}
matcher.appendTail(result); // 添加剩余部分
return result.toString(); // 返回转换后的表达式
}
// 将带分数形式转换为普通分数
public static String convertMixedFractionToImproperFraction(String mixedFraction) {
// 正则表达式匹配带分数
Pattern mixedFractionPattern = Pattern.compile("(\\d+)'(\\d+)/(\\d+)");
Matcher matcher = mixedFractionPattern.matcher(mixedFraction);
if (matcher.matches()) {
int wholeNumber = Integer.parseInt(matcher.group(1)); // 整数部分
int numerator = Integer.parseInt(matcher.group(2)); // 分子
int denominator = Integer.parseInt(matcher.group(3)); // 分母
// 将带分数转换为普通分数
int improperNumerator = wholeNumber * denominator + numerator; // 计算不合适分子
return improperNumerator + "/" + denominator; // 返回普通分数形式
} else {
// 处理未匹配带分数形式的情况(可以是普通分数形式)
return mixedFraction; // 返回原分数形式
}
}
}
FractionCalculator
这段代码实现了一个分数计算器,能够解析和计算包含分数的数学表达式。它的主要功能包括:
表达式解析:使用栈数据结构来管理数值和运算符,以支持复杂表达式的评估。
分数处理:提供了将字符串格式的分数转换为 BigFraction 对象的方法,支持加、减、乘、除等基本运算。
运算符优先级:通过优先级规则确保正确的运算顺序。
错误处理:处理了除以零的情况和无效运算符,确保计算过程中的安全性和稳定性。
public class FractionCalculator {
// 评估包含分数的数学表达式
public static BigFraction evaluateFractionExpression(String expression) throws IllegalArgumentException {
Stack<BigFraction> values = new Stack<>(); // 存储数值的栈
Stack<Character> operators = new Stack<>(); // 存储运算符的栈
// 使用字符串分割器来解析表达式
StringTokenizer tokenizer = new StringTokenizer(expression, "()+-*/", true);
while (tokenizer.hasMoreTokens()) {
String token = tokenizer.nextToken().trim(); // 获取下一个标记并去掉空格
if (token.isEmpty()) continue; // 忽略空标记
if (token.equals("(")) {
operators.push('('); // 如果是左括号,压入运算符栈
} else if (token.equals(")")) {
// 当遇到右括号时,处理直到遇到左括号
while (operators.peek() != '(') {
values.push(applyOperator(operators.pop(), values.pop(), values.pop()));
}
operators.pop(); // 弹出左括号
} else if (isOperator(token.charAt(0))) {
// 处理运算符的优先级
while (!operators.isEmpty() && precedence(operators.peek()) >= precedence(token.charAt(0))) {
values.push(applyOperator(operators.pop(), values.pop(), values.pop()));
}
operators.push(token.charAt(0)); // 压入当前运算符
} else {
values.push(parseFraction(token)); // 将标记解析为分数并压入值栈
}
}
// 处理剩余的运算符
while (!operators.isEmpty()) {
values.push(applyOperator(operators.pop(), values.pop(), values.pop()));
}
return values.pop(); // 返回最终结果
}
// 将字符串标记解析为分数
private static BigFraction parseFraction(String token) {
if (token.contains("/")) {
String[] parts = token.split("/"); // 分割分子和分母
return new BigFraction(Integer.parseInt(parts[0]), Integer.parseInt(parts[1]));
} else {
return new BigFraction(Integer.parseInt(token), 1); // 整数作为分数处理
}
}
// 检查字符是否为运算符
private static boolean isOperator(char c) {
return c == '*' || c == '/' || c == '+' || c == '-';
}
// 返回运算符的优先级
private static int precedence(char operator) {
if (operator == '*' || operator == '/') {
return 2; // 乘除优先级高
} else if (operator == '+' || operator == '-') {
return 1; // 加减优先级低
}
return -1; // 无效的运算符
}
// 根据运算符对两个分数进行运算
private static BigFraction applyOperator(char operator, BigFraction b, BigFraction a) {
try {
switch (operator) {
case '+': return a.add(b); // 加法
case '-': return a.subtract(b); // 减法
case '*': return a.multiply(b); // 乘法
case '/':
if (b.equals(BigFraction.ZERO)) {
System.err.println("Division by zero is not allowed."); // 除以零的错误处理
return null;
}
return a.divide(b); // 除法
default:
throw new IllegalArgumentException("Invalid operator: " + operator); // 无效运算符
}
} catch (NullArgumentException e) {
System.err.println(e.getMessage()); // 处理空参数异常
return null;
}
}
}
效能分析
性能分析图
模块接口
测试运行
运行结果
- 输入100个问题数,输入范围是10,返回客户端压缩包,压缩包里面是exercise文件和answer文件
项目小结
-
小组合作不仅深化了对我们编程技术的理解,更学会了团队协作的重要性。通过共同面对挑战、讨论解决方案,我们体会到了集思广益的力量。
-
遇到了不少挑战,其中最大的难点在于确保运算题目的多样性和难度控制。如何让生成器既能涵盖加、减、乘、除等基本运算,又能根据年级调整难度,成为我们重点攻克的问题。
此外,编程过程中遇到的逻辑错误和算法优化也是不容忽视的难点。通过不断试错与调整,我们学会了如何更高效地沟通与合作,最终克服了这些障碍。 -
这段经历不仅增长了知识,还培养了我们的逻辑思维能力和耐心,让我们深刻理解了合作的力量,面对难题时的坚持与探索让我们收获满满,为未来学习和工作奠定了坚实的基础。