结对项目:简易实现自动生成四则运算

结对项目:简易实现自动生成四则运算

软件工程 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 exercises = FileUtil.readAsEquation(exercisePath);
List answers = FileUtil.readAsAnswer(answerPath);
if(exercises.size() != answers.size()){
System.out.println("练习与答案数量不相同,只分析写了答案的题目");
}

//记录对错情况
List corrects = new ArrayList<>();
List 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 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 context){
try {
List 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 readAsEquation(String path){
List 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 readAsAnswer(String path){
List 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;
}
}
关键代码
用来过滤重复表达式,按顺序层层筛选,由于转换成后缀表达式,不用考虑括号

  1. 先去除运算过程含负数的

  2. 先比较结果

  3. 比较表达式是否一样

  4. 再比较包含的运算符是否相同

  5. 比较第一次运算的两数是否只是交换位置

    public static List filter(List 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 postfix1 = equation.getPostfix();
    List postfix2 = toCompare.getPostfix();
    List operators1 = equation.getOperators();
    List 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 operands1 = new ArrayList<>();
    List 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 arithmetics = new ArrayList<>();
List operands = new ArrayList<>();
List operators = new ArrayList<>();
List 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 infixToPostfix(){
Stack stack = new Stack<>();
List 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;
}

性能分析

测试运行


项目小结

这次项目对我们来说还是非常艰巨的,而且本次要求的结对编程我们也是首次接触。在完成该次项目的过程中,我们查阅了很多资料,逐步的完成一个个功能,期间遇到了很多的难题,但好在最后都得以解决了。发现合作编程需要队友间进行多交流沟通,好的合作可以大大提高开发效率。其次,还有一些需要改进的地方,例如项目计划和时间管理方面还有待提高,代码质量方面还有需要改进的地方等等。

posted @ 2023-09-27 14:33  HelpmeOOUT  阅读(74)  评论(0)    收藏  举报