结对项目:简易实现自动生成四则运算
软件工程 | https://edu.cnblogs.com/campus/gdgy/CSGrade21-12 |
---|---|
作业要求 | 个人项目 |
作业目标 | 实现一个自动生成小学四则运算题目的命令行程序 |
github链接 | https://github.com/HelpmeOOUT/RWL/tree/main/FourOperation-master |
团队成员
姓名 | 学号 |
---|---|
容伟亮 | 3121005006 |
龚杨 | 3121004994 |
PSP
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 20 | 30 |
-Estimate | -估计这个任务需要多少时间 | 30 | 50 |
Development | 开发 | 430 | 470 |
-Analysis | -需求分析 (包括学习新技术) | 150 | 80 |
-Design Spec | -生成设计文档 | 60 | 60 |
-Design Review | -设计复审 | 20 | 20 |
-Coding Standard | -代码规范 (为目前的开发制定合适的规范) | 30 | 20 |
-Design | -具体设计 | 40 | 30 |
-Coding | -具体编码 | 160 | 180 |
-Code Review | -代码复审 | 20 | 40 |
Test | 测试(自我测试,修改代码,提交修改) | 200 | 280 |
-Reporting | -报告 | 100 | 120 |
-Test Report | -测试报告 | 50 | 60 |
-Size Measurement | -计算工作量 | 50 | 60 |
-Postmortem & Process Improvement Plan | -事后总结, 并提出过程改进计划 | 30 | 40 |
合计 | 1390 | 1590 |
具体代码
项目结构接口
Main函数
public class Main {
private static Integer r = null;
private static Integer n = null;
private static String e = null;
private static String a = null;
public static void main(String[] args) {
processArgs(args);
if(e != null && a != null){
//分析答案
analyse(e, a, "Grade.txt");
}else if(r != null && n != null){
//生成题目
generateExercise(n, r);
}else{
System.out.println("参数错误,请重新启动并输入正确参数");
}
}
/**
* @param exercisePath 内含表达式的文件路径
* @param answerPath 存放计算结果的文件路径
* @param gradePath 统计对错情况及对应题目序号的文件路径
* */
public static void analyse(String exercisePath, String answerPath, String gradePath){
List<Equation> exercises = FileUtil.readAsEquation(exercisePath);
List<Float> answers = FileUtil.readAsAnswer(answerPath);
if(exercises.size() != answers.size()){
System.out.println("练习与答案数量不相同,只分析写了答案的题目");
}
//记录对错情况
List<String> corrects = new ArrayList<>();
List<String> wrongs = new ArrayList<>();
for(int i=0;i<answers.size();i++){
if(Math.abs(answers.get(i) - exercises.get(i).getResult()) < 0.000001){
corrects.add(i+"");
}else{
wrongs.add(i+"");
}
}
StringBuilder sb1 = new StringBuilder();
sb1.append("Correct:").append(corrects.size()).append("(");
sb1.append(String.join(",", corrects)).append(")");
StringBuilder sb2 = new StringBuilder();
sb2.append("Wrong:").append(wrongs.size()).append("(");
sb2.append(String.join(",", wrongs)).append(")");
FileUtil.write(gradePath, Arrays.asList(sb1.toString(), sb2.toString()));
}
/**
* @param exerciseNum 所需生成的题目数量
* @param valueLimitation 指定范围参数,操作数及操作数分母<valueLimitation
* */
public static void generateExercise(int exerciseNum, int valueLimitation){
List<Equation> list = new ArrayList<>();
Random r = new Random();
//随机生成表达式,并筛掉重复的,直到数量达到目标
for(;list.size()<exerciseNum;) {
int operandNo = r.nextInt(3) + 2;
int operatorNo = operandNo - 1;
int bracketsNo = 1;
list.add(Equation.generate(operandNo, operatorNo, bracketsNo, 1, valueLimitation));
Equation.filter(list);
}
//写入文件
FileUtil.write("Exercises.txt", list.stream().map(Equation::toString).toList());
FileUtil.write("Answers.txt", list.stream().map(e -> e.getResult() + "").toList());
}
/**
* @param args 命令行参数数组
* */
public static void processArgs(String[] args){
for(int i=0;i< args.length;i++){
switch (args[i]){
//可能有输入错误
case "-r" -> r = Integer.parseInt(args[i+1]);
case "-n" -> n = Integer.parseInt(args[i+1]);
case "-e" -> e = args[i+1];
case "-a" -> a = args[i+1];
}
}
}
/**
* @param equation1 表达式1
* @param equation2 表达式2
* @return 返回两表达式是否重复
* */
public static boolean checkRepetition(Equation equation1, Equation equation2){
return true;
}
}
io流代码
/**
* 文件工具类
* @author 3121005035, OuroborosNo2
* */
public class FileUtil {
/**
* 将内容写入指定路径文件
* @param path 文件路径
* @param context 写入内容
* @return 成功与否
* */
public static boolean write(String path, List<String> context){
try {
List<String> list = new ArrayList<>();
//加上序号
for(int i=0;i<context.size();i++) {
list.add(i, i + "、" + context.get(i));
}
Files.write(Paths.get(path), list);
}catch (Exception e){
e.printStackTrace();
return false;
}
return true;
}
/**
* 从指定路径文件读取成表达式数组
* @param path 文件路径
* @return 表达式数组
* */
public static List<Equation> readAsEquation(String path){
List<Equation> lines;
try {
lines = Files.readAllLines(Paths.get(path)).stream().map(s -> {
try {
//除去序号
return Equation.parse(s.substring(s.indexOf("、")+1));
} catch (OperandException e) {
throw new RuntimeException(e);
}
}).toList();
}catch (Exception e){
e.printStackTrace();
return null;
}
return lines;
}
public static List<Float> readAsAnswer(String path){
List<Float> lines;
try {
//除去序号
lines = Files.readAllLines(Paths.get(path)).stream().map(s -> Float.parseFloat(s.substring(s.indexOf("、")+1))).toList();
}catch (Exception e){
e.printStackTrace();
return null;
}
return lines;
}
}
关键代码
用来过滤重复表达式,按顺序层层筛选,由于转换成后缀表达式,不用考虑括号
-
先去除运算过程含负数的
-
先比较结果
-
比较表达式是否一样
-
再比较包含的运算符是否相同
-
比较第一次运算的两数是否只是交换位置
public static List<Equation> filter(List<Equation> list){ for(int i=0;i < list.size();i++){ Equation equation = list.get(i); //如果运算过程含负数,则跳过 if(equation.isOf()){ list.remove(equation); //remove会整体前移 i--; continue; } //和整个list比较 //标签方便下面层层嵌套能直接goto出来 flag: for(int o=0;o< list.size();o++){ Equation toCompare = list.get(o); //删除后有空位,要跳过 if(toCompare == null){ continue; } //遇到自己就跳过 if(equation == toCompare){ continue; } //先比较结果 if(Math.abs(equation.getResult() - toCompare.getResult()) < 0.000001) { //结果相同,看是否完全一样 if(equation.equals(toCompare)){ list.remove(equation); //remove会整体前移 i--; break flag; } //再比较运算符 List<Arithmetic> postfix1 = equation.getPostfix(); List<Arithmetic> postfix2 = toCompare.getPostfix(); List<Operator> operators1 = equation.getOperators(); List<Operator> operators2 = toCompare.getOperators(); //有不同运算符就保留 if(operators1.size() != operators2.size()){ break flag; } for(int j=0;j<operators1.size();j++){ if(operators1.get(j) != operators2.get(j)){ break flag; } } //运算符相同,只比较第一次计算的两数字是否交换位置 //找到第一个运算符,取前两个数字 List<Operand> operands1 = new ArrayList<>(); List<Operand> operands2 = new ArrayList<>(); for(int j=0;j<postfix1.size();j++){ if(postfix1.get(j) instanceof Operator){ operands1.add((Operand) postfix1.get(j-1)); operands1.add((Operand) postfix1.get(j-2)); break; } } for(int j=0;j<postfix1.size();j++){ if(postfix2.get(j) instanceof Operator){ operands2.add((Operand) postfix2.get(j-1)); operands2.add((Operand) postfix2.get(j-2)); break; } } //比较两对数字 if((operands1.get(0).equals(operands2.get(0)) || operands1.get(0).equals(operands2.get(1))) && (operands1.get(1).equals(operands2.get(0)) || operands1.get(1).equals(operands2.get(1)))){ list.remove(equation); //remove会整体前移 i--; break flag; }else{ //两对数字不相同,保留 break flag; } }else{ //结果不一样,保留 break flag; } } } return list.stream().toList(); }
用来生成随机表达式
@param operandNo 操作数数量
@param operatorNo 运算符数量
@param bracketsNo 括号数量
@param lowEnd 生成表达式中操作数和真分数分母范围的下限
@param upEnd 生成表达式中操作数和真分数分母范围的上限
@return 生成的表达式
public static Equation generate(int operandNo, int operatorNo, int bracketsNo
, int lowEnd, int upEnd){
Random r = new Random();
int scope = upEnd - lowEnd;
List<Arithmetic> arithmetics = new ArrayList<>();
List<Operand> operands = new ArrayList<>();
List<Operator> operators = new ArrayList<>();
List<Brackets> brackets = new ArrayList<>();
try {
for (int i = 0; i < operandNo; i++) {
// 操作数类型 自然数(0),真分数(1)
int type = r.nextInt(10)%2;
if(0 == type){
//生成随机整数
operands.add(new Operand(type, r.nextInt(scope) + lowEnd + ""));
}else if (1 == type){
//生成真分数
int denominator = r.nextInt(scope) + lowEnd + 1;
// 分子 > 0
int numerator = r.nextInt(denominator - 1) + 1;
String str = numerator + "/" + denominator;
operands.add(new Operand(type, str));
}
}
for (int i = 0; i < operatorNo; i++) {
// 除去等号
int index = r.nextInt(4) + 1;
operators.add(Operator.getByIndex(index));
}
for (int i = 0; i < bracketsNo; i++) {
brackets.add(Brackets.getByIndex(0));
brackets.add(Brackets.getByIndex(1));
}
for (int i = 0; i < operands.size(); i++) {
if(operands.get(i) != null){
arithmetics.add(operands.get(i));
}
if(i == operands.size()-1){
break;
}
if(operators.get(i) != null) {
arithmetics.add(operators.get(i));
}
}
}catch (Exception e){
e.printStackTrace();
return null;
}
return new Equation(arithmetics);
}
将中缀表达式转换为后缀表达式
@return 返回转换结果
public List<Arithmetic> infixToPostfix(){
Stack<Arithmetic> stack = new Stack<>();
List<Arithmetic> postfix = new ArrayList<>();
for(int start = 0; start < infix.size(); start++){
//如果是运算符
if(infix.get(start).priority > 0) {
//栈空 或 "(" 或 符号优先级>栈顶符号 且 不为")" 直接进栈
if (stack.isEmpty() || infix.get(start).priority == 3 ||
((infix.get(start).priority > stack.peek().priority) && infix.get(start).priority < 4)) {
stack.push(infix.get(start));
} else if (!stack.isEmpty() && infix.get(start).priority <= stack.peek().priority) {
//栈非空 且 符号优先级≤栈顶符号, 出栈; 直到 栈为空 或 遇到了"("
while (!stack.isEmpty() && infix.get(start).priority <= stack.peek().priority) {
if (stack.peek().priority == 3) {
stack.pop();
break;
}
postfix.add(stack.pop());
}
stack.push(infix.get(start));
} else if (infix.get(start).priority == 4) {
//")",依次出栈直到空栈或遇到第一个"(",此时"("出栈
while (!stack.isEmpty()) {
if (stack.peek().priority == 3) {
stack.pop();
break;
}
postfix.add(stack.pop());
}
}
}else if(infix.get(start).priority == -1){
postfix.add(infix.get(start));
}
}
while(!stack.isEmpty()){
postfix.add(stack.pop());
}
return postfix;
}
性能分析
测试运行
项目小结
这次项目对我们来说还是非常艰巨的,而且本次要求的结对编程我们也是首次接触。在完成该次项目的过程中,我们查阅了很多资料,逐步的完成一个个功能,期间遇到了很多的难题,但好在最后都得以解决了。发现合作编程需要队友间进行多交流沟通,好的合作可以大大提高开发效率。其次,还有一些需要改进的地方,例如项目计划和时间管理方面还有待提高,代码质量方面还有需要改进的地方等等。