结对项目

项目信息

项目人员:

姓名 学号
谢建豪 3122004793
陈志豪 3122004778
这个作业属于哪个课程 软件工程2024-广东工业大学
这个作业要求在哪里 结对项目
这个作业的目标 学习并应用两人合作项目的过程和方法
GitHub地址 GitHub地址

开发环境

开发工具 IntelliJ IDEA 2023.2.1
编程语言 java
运行环境 Java(TM) SE Runtime Environment Oracle GraalVM 21.0.2+13.1 (build 21.0.2+13-LTS-jvmci-23.1-b30)
构建工具 maven
编译环境 java version "21.0.2"

需求分析

设计一个支持小学范围内的四则运算题目生成器,并且可以生成答案和进行答案的校对,要求可以通过命令行或者图像界面进行交互。

效能分析


设计实现过程

设计了Number类来存放整数和分数并判断其规范性,设计Symbol类来进行四则运算使其能进行整数与分数的混合运算,设置FileUtilss来进行文件操作,设置NumberUtil用来对多项运算表达式使用后缀表达式进行处理

代码说明

Number类

package com.jieduixiangmu;

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 表示数字的类
 */
public class Number {
    public static final int UNEXIST=-1;//不存在
    public static final int INT=0;//整形
    public static final int PROPER_FRACTION=1;//真分数
    public static final int WITH_FRACTION =2;//带分数
    public static final int OPERATOR=3;//符号
    public static final int FAKE_FRACTION=4;//假分数
    public static Number ERROR=new Number(null,UNEXIST);
    public static Number FAKE=new Number(null,FAKE_FRACTION);
    public static Map<String,Symbol> OPERATORS=new HashMap<>();
    static {
        OPERATORS.put("+",Symbol.ADD);
        OPERATORS.put("-",Symbol.SUB);
        OPERATORS.put("*",Symbol.MUL);
        OPERATORS.put("/",Symbol.DIV);
        OPERATORS.put("(",Symbol.LEFT_BRACKET);
        OPERATORS.put(")",Symbol.RIGHT_BRACKET);
    }
    int type;//表示这个数字是整数,还是真分数,假分数
    String expression;//表示这个数的元数据,如1(整数)    1/2(真分数)  1 1/2(带分数)
    public Number(String expression,int type){
        this.expression=expression;
        this.type=type;
    }

    @Override
    public String toString() {
        return expression;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Number number = (Number) o;
        return type == number.type && Objects.equals(expression, number.expression);
    }

    /**
     * 判断这个类是否合法
     * @return
     */
    public boolean isLegal(){
        if(expression==null||expression.isEmpty())return false;
        boolean legal=true;
        switch (type) {
            //操作符
            case OPERATOR -> {
                //如果是在定义的操作符里,则说明该ex是合法的操作符
                if(Number.OPERATORS.get(expression)!=null){
                    return true;
                }
            }
            //整数
            case INT -> {
                try {//使用Integer类的字符串强转数字,如果报错,说明该字符串本身就不代表数字
                    Integer.valueOf(expression);
                } catch (Exception e) {
                    legal=false;
                }
            }
            //真分数
            case PROPER_FRACTION -> {
                String[] split = expression.split("/");
                if(split.length!=2)legal=false;
                try {
                    //使用Integer类的字符串强转数字,如果报错,说明该字符串本身就不代表数字
                    if(Integer.valueOf(split[0])>=Integer.valueOf(split[1]))legal=false;
                }catch (Exception e){
                    legal=false;
                }
            }
            //带分数
            //TODO 带分数的处理逻辑仍需修改
            case WITH_FRACTION -> {
                String regex = "(\\d+)\\s(\\d+)/(\\d+)";
                Pattern pattern = Pattern.compile(regex);
                Matcher matcher = pattern.matcher(expression);
                String[] split = new String[3];
                if (matcher.matches()) {
                    split[0] = matcher.group(1);
                    split[1] = matcher.group(2);
                    split[2] = matcher.group(3);
                }
                if(split.length!=3)legal=false;
                try {
                    //TODO 改成处理带分数是否合法的逻辑
                    Integer.valueOf(split[0]);
                    if(Integer.valueOf(split[1])>=Integer.valueOf(split[2]))legal=false;
                }catch (Exception e){
                    legal=false;
                }
            }
            //默认,如果都没匹配上,说明type不合法!
            default -> {
                legal=false;
            }
        }
        return legal;
    }

    /**
     * 工厂模式创建Number实例,以输入的表达式为元数据
     * @param expression
     * @return
     */
    public static final Number forExpression(String expression){
        //若为空,则返回错误
        if(expression==null||expression.isEmpty())return ERROR;
        //若操作符集中有该符号,则直接创建对象
        if(Number.OPERATORS.get(expression)!=null){
            return new Number(expression,OPERATOR);
        }
        //若能转化为整数,则说明该表达式本身就是整数
        try {
            Integer i = Integer.valueOf(expression);
            return new Number(expression,INT);
        }catch (Exception e){

        }
        //若能转化为整数,则说明该表达式本身就是真分数
        try {
            //由/分割,且只能分割为两个,否则错误
            String[] split = expression.split("/");
            if(split==null||split.length!=2)return ERROR;
            Integer left = 0;
            Integer right = 0;
            left = Integer.valueOf(split[0]);
            right = Integer.valueOf(split[1]);
            //左右两个数不能相等,左边必须小于右边
            if(left>=right)return new Number(expression,FAKE_FRACTION);
            else return new Number(expression,PROPER_FRACTION);
        }catch (Exception e){

        }
        //TODO 此处处理带分数的逻辑
        //若能转化为整数,则说明该表达式本身就是带分数
        try {
            //通过'或者/分割表达式
            String[] split = expression.split("'|/");
            if(split.length!=3)return ERROR;
            //TODO 改成处理带分数是否合法的逻辑
            Integer.valueOf(split[0]);
            if(Integer.valueOf(split[1])>=Integer.valueOf(split[2]))return ERROR;
            return new Number(expression,WITH_FRACTION);
        }catch (Exception e){

        }
        return ERROR;
    }

}

Symbol类

package com.jieduixiangmu;

import org.apache.commons.lang3.math.Fraction;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

import static com.jieduixiangmu.Number.*;

public class Symbol {
    public Compute operation;

    public Symbol(Compute operation) {
        this.operation = operation;
    }
    public static void p(Number...args){
//        for (Number arg : args) {
//            System.out.println(arg);
//        }
    }

    //加号+ 1'1/2
    public static Symbol ADD = new Symbol((args) -> {
        p(args);
        if (args == null || args.length != 2) return ERROR;
        String arr1 = args[0].toString();
        String arr2 = args[1].toString();
        arr1 = arr1.replaceAll("'", " ");
        arr2 = arr2.replaceAll("'", " ");
        Fraction n1 = Fraction.getFraction(arr1);
        Fraction n2 = Fraction.getFraction(arr2);
        Fraction sum = n1.add(n2);
        int i = 0;
        String out;
        if (sum.getDenominator() == 1) {
            i = sum.getProperWhole();
            return Number.forExpression(String.valueOf(i));
        } else {
            out = sum.toProperString();
            out = out.replaceAll(" ", "'");
            return Number.forExpression(out);
        }
    });
    //减号-
    public static Symbol SUB = new Symbol((args) -> {
        p(args);
        if (args == null || args.length != 2) return ERROR;
        String arr1 = args[0].toString();
        String arr2 = args[1].toString();
        arr1 = arr1.replaceAll("'", " ");
        arr2 = arr2.replaceAll("'", " ");
        Fraction n1 = Fraction.getFraction(arr1);
        Fraction n2 = Fraction.getFraction(arr2);
        Fraction sum = n1.subtract(n2);
        int i = 0;
        String out;
        if (sum.getDenominator() == 1) {
            i = sum.getProperWhole();
            return Number.forExpression(String.valueOf(i));
        } else {
            out = sum.toProperString();
            out = out.replaceAll(" ", "'");
            return Number.forExpression(out);
        }
    });
    //乘号*
    public static Symbol MUL = new Symbol((args) -> {
        p(args);
        if (args == null || args.length != 2) return ERROR;
        String arr1 = args[0].toString();
        String arr2 = args[1].toString();
        arr1 = arr1.replaceAll("'", " ");
        arr2 = arr2.replaceAll("'", " ");
        Fraction n1 = Fraction.getFraction(arr1);
        Fraction n2 = Fraction.getFraction(arr2);
        Fraction sum = n1.multiplyBy(n2);
        int i = 0;
        String out;
        if (sum.getDenominator() == 1) {
            i = sum.getProperWhole();
            return Number.forExpression(String.valueOf(i));
        } else {
            out = sum.toProperString();
            out = out.replaceAll(" ", "'");
            return Number.forExpression(out);
        }
    });
    //除号/
    public static Symbol DIV = new Symbol(args -> {
        p(args);
        if (args == null || args.length != 2) return ERROR;
        String arr1 = args[0].toString();
        String arr2 = args[1].toString();
        arr1 = arr1.replaceAll("'", " ");
        arr2 = arr2.replaceAll("'", " ");
        Fraction n1 = Fraction.getFraction(arr1);
        Fraction n2 = Fraction.getFraction(arr2);
        if (n2.equals(Fraction.ZERO))return ERROR;
        Fraction sum = n1.divideBy(n2);
        int i =0;
        String out ;
        if(sum.getDenominator()==1){
            i = sum.getProperWhole();
            return Number.forExpression(String.valueOf(i));
        }
        else {
            out= sum.toProperString();
            out = out.replaceAll(" ", "'");
            return Number.forExpression(out);
        }
    });
    //左括号
    public static Symbol LEFT_BRACKET = new Symbol(args -> {
        return Number.forExpression("(");
    });
    //右括号
    public static Symbol RIGHT_BRACKET = new Symbol(args -> {
        return Number.forExpression(")");
    });
    //撇号'
    public static Symbol APOSTROPHE = new Symbol(args -> {
        return Number.forExpression("‘");
    });

    public Number compute(Number... args) {
        return this.operation.compute(args);
    }

}

@FunctionalInterface
interface Compute {
    public Number compute(Number... args);
}

NumberUtil类

package com.jieduixiangmu;

import java.util.*;

import static com.jieduixiangmu.Number.*;

/**
 * 本类用于实现基于Number类的后缀表达式遍历
 */
public class NumberUtil {
    public static final int NUMBER=1;
    public static final int OPERATOR=2;
    public static final int ERROR=3;
    /**
     * 根据规律切割字符串并构建以Number为基础的中缀表达式,用list存储
     * @param expression
     */
    public static List<Number> splitExpression(String expression) throws Exception {
        if(expression==null||expression.isEmpty()){
            return new ArrayList<>();
        }
        List<Number>inFix=new ArrayList<>();
        //获取字符
        char[] charArray = expression.toCharArray();
        //遍历
        for (int i = 0; i < charArray.length; i++) {
            char c=charArray[i];
            //判断该字符是什么类型
            switch (getType(c)){
                //数字
                case NUMBER -> {
                    //判断是否是连续的数字字符,用于判断两位数以上的数字,如22
                    int j=0;
                    Integer num=0;
                    boolean isWith=false;
                    for (j = i+1; j < charArray.length; j++) {
                        //如果不是数字则终止循环
                        char c1=charArray[j];
                        if("'".equals(String.valueOf(c1))){
                            isWith=true;
                        }
                        if(getType(c1,isWith,num)!=NUMBER) {
                            break;
                        }else {
                            if(c1=='/'){
                                num++;
                            }
                        }
                        //下一个是数字则继续
                    }
                    inFix.add(Number.forExpression(
                            String.valueOf(charArray,//原数据
                                    i,//起始索引
                                    j-i//索引的范围
                            ))
                    );
                    i=j-1;
                }
                //操作符
                case OPERATOR -> {
                    //判断是否是连续的数字字符,用于判断两位数以上的数字,如22
                    int j=0;
                    for (j = i+1; j < charArray.length; j++) {
                        //如果不是操作符则终止循环
                        char c1=charArray[j];
                        if(getType(c1)!=OPERATOR) break;
                        //下一个是数字则继续
                    }
                    inFix.add(Number.forExpression(
                            String.valueOf(charArray,//原数据
                                    i,//起始索引
                                    j-i//索引的范围
                            ))
                    );
                    i=j-1;
                }
                //两不
                case ERROR -> throw new Exception("既不是数字也不是操作符!");
            }
        }
        return inFix;
    }

    /**
     * 将中缀表达式转化为后缀表达式
     * @param infix 中缀表达式
     * @return postfix 后缀表达式
     */
    public static List<Number> infix2Postfix(List<Number>infix){
        if(infix==null||infix.isEmpty())return new ArrayList<>();
        //要保存到的后缀表达式队列
        MyList<Number> postfix=new MyList<>();
        //操作符栈
        MyList<Number> operatorStack=new MyList<>();
        try {
            infix.forEach(number -> {
                switch (number.type){
                    //如果是数字,则入数字栈
                    case INT,WITH_FRACTION,PROPER_FRACTION->{
                        postfix.add(number);
                    }
                    //如果是运算符,则入运算符栈
                    case Number.OPERATOR -> {
                        Number last = operatorStack.tail();
                        if(last!=null){
                            operatorStack.push(last);
                        }
                        //如果栈顶为空,则直接入栈
                        if(last==null){
                            operatorStack.push(number);
                        }
                        //如果该元素为右括号,则直接全部出栈,直到遇到左括号
                        else if (number.expression.equals(")")) {
                            //只要非空就一致循环下去
                            while (!operatorStack.isEmpty()){
                                Number o = operatorStack.pop();
                                //如果遇到左括号,则停止循环
                                if(o.expression.equals("("))break;
                                //如果后缀表达式为空,则取两个
                                postfix.add(o);
                            }
                        }
                        //比较栈顶优先级
                        else if(getPriority(last)<getPriority(number)){
                            operatorStack.push(number);
                        }
                        //
                        else {
                            MyList<Number>queue=new MyList<>();
                            while (true){
                                Number head = operatorStack.tail();
                                if(head!=null)operatorStack.push(head);
                                //如果栈顶小于要入栈的元素,则直接入栈
                                if(getPriority(head)<getPriority(number)){
                                    operatorStack.push(number);
                                    while (!queue.isEmpty()){
                                        Number tail = queue.tail();
                                        postfix.add(tail);
                                    }
                                    break;
                                }
                                //
                                else {
                                    queue.add(operatorStack.tail());
                                }
                            }
                        }
                    }
                    //如果不存在,则报错
                    default -> {
                        return;
                    }
                }
            });
            operatorStack.forEach(number -> {
                postfix.add(number);
            });
        }catch (Exception e){

        }
        return postfix;
    }

    /**
     * 根据表达式获取后缀表达式
     * @param expression 算式表达式
     * @return 后缀表达式
     * @throws Exception 异常
     */
    public static List<Number> getPostfix(String expression) throws Exception {
        return infix2Postfix(splitExpression(expression));
    }

    /**
     *
     * @param c 单个字符
     * @return int
     */
    public static int getType(char c){
        if((c<='9'&&c>='0')||c=="'".toCharArray()[0])return NUMBER;
        else if(Number.OPERATORS.get(String.valueOf(c))!=null){
            return OPERATOR;
        }
        return ERROR;
    }
    public static int getType(char c,boolean isWith,Integer num){
        if(isWith&&num==0){
            if(c=='/')return NUMBER;
        }
        if((c<='9'&&c>='0')||c=="'".toCharArray()[0])return NUMBER;
        else if(Number.OPERATORS.get(String.valueOf(c))!=null){
            return OPERATOR;
        }
        return ERROR;
    }

    /**
     * 判断对应的操作符是否存在
     * @param data 原始数据
     * @param offset 初始偏移量
     * @param count 偏移量
     * @return boolean 是否为运算符
     */
    public static boolean isOperator(char[] data, int offset, int count){
        System.out.println(String.valueOf(data,offset,count));
        return Number.OPERATORS.get(String.valueOf(data,offset,count))!=null;
    }
    public static int getPriority(Number o1){
        if(o1==null)return -1;
        switch (o1.expression){
            case "+","-"-> {
                return 1;
            }
            case "*","/"-> {
                return 2;
            }
            case "(",")"->{
                return 100;
            }
        }
        return -1;
    }
    public static class MyList<T> extends LinkedList<T> implements List<T>{
        public T tail(){
            try {
                return  pop();
            }catch (Exception e){
                return null;
            }
        }
    }

    /**
     *
     * @param postFix
     * @return
     */
    public static Number calculate(List<Number> postFix){
        if(postFix==null||postFix.isEmpty()){
            return Number.ERROR;
        }
        MyList<Number>numbers=new MyList<>();
        postFix.forEach(number -> {
            switch (number.type){
                case Number.OPERATOR -> {
                    Number n1 = numbers.tail();
                    Number n2 = numbers.tail();
                    Symbol symbol = OPERATORS.get(number.expression);
                    Number compute = symbol.compute(n2, n1);
                    numbers.push(compute);
                }
                default -> {
                    numbers.push(number);
                }
            }
        });
        return numbers.tail();
    }
}

主函数

package com.jieduixiangmu;


import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.IntFunction;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import static cn.hutool.core.util.RandomUtil.randomInt;


public class MainActivity {
    public static final int INT=0;
    public static final int PROPER_FRACTION=1;
    public static final int WITH_FRACTION=2;
    public static final int ADD=0;
    public static final int SUB=1;
    public static final int MUL=2;
    public static final int DIV=3;
    public static final int MAX=10;
    public static final int MIN=1;
    public static String getNumber(int max,int type){
        max=max<=0?MAX:max;
        type=type<0?0:(Math.min(type, 1));
        int i =  randomInt(0, 3);
        if(type==0||type==-1){
            i=INT;
        }
        switch (i){
            case INT -> {
                return String.valueOf(randomInt(MIN,MAX));
            }
            case PROPER_FRACTION -> {
                int left=randomInt(MIN,MAX);
                int right=randomInt(left,MAX);
                if(left>=right)return getNumber(max,type);
                else return String.format("%d/%d",left,right);
            }
            case WITH_FRACTION -> {
                int real=randomInt(MIN,MAX);
                int left=randomInt(MIN,MAX);
                int right=randomInt(left,MAX);
                if(left>=right)return getNumber(max,type);
                else return String.format("%d'%d/%d",real,left,right);
            }
        }
        return "1";
    }
    public static String getOperator(){
        int i =  randomInt(0, 4);
        switch (i){
            case ADD -> {
                return  "+";
            }
            case SUB -> {
                return  "-";
            }
            case MUL -> {
                return  "*";
            }
            case DIV -> {
                return  "/";
            }
        }
        return "+";
    }
    public static String getExpression(int max){
        max=max<=0?MAX:max;
        int i = randomInt(1, 4);
//        System.out.println(i);
        String expression="";
        int op=0;//0为添加数字,1为添加符号
        int sum=0;//记录添加了几个数字,sum必须永远比i多1
        int opera=-1;//保证第一个数一定是整数!
        while (sum!=i+1){
            if(op==0){
                expression+=getNumber(max,opera);
                sum++;
                op=1;
            }else {
                String operator = getOperator();
                switch (operator){
                    case "+","-"->{
                        opera=1;
                    }
                    case "*","/"->{
                        opera=0;
                    }
                }
                expression+=operator;
                op=0;
            }
        }
        return expression;
    }
    public static void run(String[]args) throws Exception {
        String Exercises="Exercises.txt";
        String Answers="Answers.txt";
        String exerciseFile="";
        String answerFile="";
        boolean isEx=false;
        boolean isAn=false;
        int sum=5;
        int max=10;
        for (int i = 0; i < args.length; i++) {
            if(args[i].equals("-e")){
                exerciseFile=args[i+1];
                isEx=true;
            }
            if(args[i].equals("-a")){
                answerFile=args[i+1];
                isAn=true;
            }
            if(args[i].equals("-n")){
                sum=Integer.valueOf(args[i+1]);
            }
            if(args[i].equals("-r")){
                max=Integer.valueOf(args[i+1]);
            }
        }
        List<String>questions=new ArrayList<>();
        List<String>answers=new ArrayList<>();
        for (int i = 0; i < sum; i++) {
            String expression = getExpression(max);
            List<Number> postfix = NumberUtil.getPostfix(expression);
//            System.out.println(expression);
            Number calculate = NumberUtil.calculate(postfix);
            questions.add(expression);
            answers.add(calculate.expression);
        }
        for (int i = 0; i < questions.size(); i++) {
            String expression = questions.get(i);
            String answer = answers.get(i);
            String text=String.format("%d.%s=\n",i+1,expression);
            String answerText = String.format("%d.%s\n", i + 1, answer);
            FileUtilss.append(Exercises,text);
            FileUtilss.append(Answers,answerText);
        }
        if(isEx&&isAn){
            String read = FileUtilss.read(exerciseFile);
            String answersssss = FileUtilss.read(answerFile);
            String[] answr = answersssss.split("\n");
            String[] split = read.split("=\r\n");
            String Correct="(";
            String Wrong="(";
            List<String>cor=new ArrayList<>();
            List<String>wro=new ArrayList<>();
            for (int i = 0; i < split.length; i++) {
                if(split[i].equals(""))continue;
                String s = split[i];
                String[] split1 = s.split("\\.");
                Number calculate = NumberUtil.calculate(NumberUtil.getPostfix(split1[1]));
                if(answr[i].split("\\.")[1].equals(calculate.toString())){
                    cor.add(split1[0]);
                }else {
                    wro.add(split1[0]);
                }
            }
            for (int i = 0; i < cor.size(); i++) {
                Correct+=cor.get(i);
                if(i!=cor.size()-1){
                    Correct+=",";
                }else {
                    Correct+=")";
                }
            }Correct=cor.size()+Correct;
            if(cor.isEmpty()){
                Correct="0()";
            }

            for (int i = 0; i < wro.size(); i++) {
                Wrong+=wro.get(i);
                if(i!=wro.size()-1){
                    Wrong+=",";
                }else {
                    Wrong+=")";
                }
            }Wrong=wro.size()+Wrong;
            if(wro.isEmpty()){
                Wrong="0()";
            }

            FileUtilss.append("Grade.txt",String.format("Correct:%s\nWrong:%s\n",Correct,Wrong));
        }
    }
    public static void main(String[] args) {
        try {
            run(args);
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

测试运行

测试生成题目

PSP表格

PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
Planning 计划
· Estimate · 估计这个任务需要多少时间 10 30
Development 开发
· Analysis · 需求分析 (包括学习新技术) 300 120
· Design Spec · 生成设计文档 240 300
· Design Review · 设计复审 (和同事审核设计文档) 120 180
· Coding Standard · 代码规范 (为目前的开发制定合适的规范) 60 60
· Design · 具体设计 180 240
· Coding · 具体编码 480 600
· Code Review · 代码复审 60 60
· Test · 测试(自我测试,修改代码,提交修改) 120 180
Reporting 报告
· Test Report · 测试报告 60 60
· Size Measurement · 计算工作量 60 30
· Postmortem & Process Improvement Plan · 事后总结, 并提出过程改进计划 60 30
合计 1750 1890

项目小结

两人合作项目会一起产生很多对解决问题和实现功能的想法,通过充分的沟通和一些工具的利用提升了合作完成项目的效率。
每个人都能从对方那里学习到一些新的想法与思路,在讨论后将更有效率的实现想法。
在这次结对项目中,我们互相学习、互相支持,共同成长。通过合作,我们更加深入地了解了彼此的工作方式和思维模式,建立了良好的合作关系。

posted @ 2024-03-26 14:52  谢建豪  阅读(12)  评论(0编辑  收藏  举报