结对作业

结对作业

结对成员:陈龙杰(3119005319) 黄仲宏(3119005324)

软件工程班级 19级网工34班
作业要求 作业3-结对项目
作业目标 熟悉个人项目开发流程 学会使用PSP表格 初步掌握Git 论文查重算法的实现 学会使用单元测试

GitHub项目地址

一.PSP表格

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

二.类与函数的设计实现

  • 相关的类结构与分析:
  • 具体的几个类及作用
    • CreatUtil类:该类采用了较多的随机生成数来实现相对随机,同时在每一步都有进行必要的数据校验,以保证数据的准确度。
    • ProcessUtil类:提供了计算过程中的一些操作方法,为计算提供了坚实的后盾
    • CalculateUtil类:将计算方法进行抽离,减低耦合度,也有进行数据的判断,保证了系统的健壮性
    • CheckUtil类:对式子的生成进行校验,将式子进行拆分,然后进行比较,并且也对答案进行比较,双重保证了式子的唯一性
    • FileDao方法:对文件的导入和导出进行封装,也就是进行数据持久化操作,方法中有对于文件格式等进行相应的判断,杜绝了一些异常的出现
    • Operator枚举:对运算符相关信息进行封装,避免多次的 new 减低效能
    • MainUI类:用户交互的主页面
    • UserUI类:用户填写等相关操作的页面

三.主要代码说明

CreatUtil类中定义了生成随机数和判断方法:creat方法用于随机生成式子,creatNum方法可随机生成操作数,creatSign方法用于随机生成符号,formulaNum方法:设定随机生成一定数目的式子,numRange方法:判断操作数是否超过最大值。


import main.com.examination.commons.Operator;
import main.com.examination.dao.FileDao;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Scanner;
import java.util.logging.Logger;

public class CreatUtil {

    //日志输出
    private static final Logger logger = Logger.getLogger("CreatUtil");
    List<StringBuilder> formula;
    //备份式子,存储"分子/分母"结构的式子,便于结果计算
    List<StringBuilder> answer;
    StringBuilder extraCopy ;

    public List<StringBuilder> getFormula() {
        return formula;
    }

    public List<StringBuilder> getAnswer() {
        return answer;
    }

    public StringBuilder creat(int maxNum) {
        StringBuilder formula = new StringBuilder();
        extraCopy = new StringBuilder(" ");
        //符号个数 (1,2,3)
        int signNum = (int)(Math.random()*3+1);
        creatNum(formula,maxNum);
        for(int i=0; i<signNum; i++) {
            creatSign(formula);
            creatNum(formula,maxNum);
        }
        formula.append(Operator.EQUAL_SIGN.getExpress() +" ");
        return formula;
    }

    public StringBuilder creatNum(StringBuilder formula,int maxNum) {
    	int numerator,denominator,type;
		type = (int)(Math.random()*2);
        //生成整数
		if(type==0) {
			do {
				numerator =(int)(Math.random()*10);
			}while(numerator > maxNum);
            //备份分子/分母
			extraCopy.append(numerator+"/"+1+" ");
			formula.append(numerator+" ");
		}
		else {
			do {
                //随机生成分子
				numerator = (int)(Math.random()*10);
                //保证分母不等于0
				while((denominator=(int)(Math.random()*10))==0);
			}while(!numRange(numerator, denominator,maxNum));
            //备份分子/分母
			extraCopy.append(numerator+"/"+denominator+" ");
			formula.append(ProcessUtil.creatNum(numerator, denominator));
		}
		return formula;
    }

    public StringBuilder creatSign(StringBuilder formula) {
        //符号类型(+ - * /)
        int signType = (int)(Math.random()*4+1);
        switch (signType){
            case 1 :
                formula.append(Operator.PLUS_SIGN.getExpress());
                extraCopy.append(Operator.PLUS_SIGN.getExpress());
                break;
            case 2 :
                formula.append(Operator.MINUS_SIGN.getExpress());
                extraCopy.append(Operator.MINUS_SIGN.getExpress());
                break;
            case 3 :
                formula.append(Operator.MULTIPLIED_SIGN.getExpress());
                extraCopy.append(Operator.MULTIPLIED_SIGN.getExpress());
                break;
            case 4 :
                formula.append(Operator.DIVISION_SIGN.getExpress());
                extraCopy.append(Operator.DIVISION_SIGN.getExpress());
                break;
            default:

        }
        extraCopy.append(" ");
        formula.append(" ");
        return formula;
    }

    public void formulaNum(int num, int maxNum) throws IOException {
        Long beginTime = System.currentTimeMillis();
        //存放拆分完的式子
        List<List<String>> formulaLists = new ArrayList<List<String>>(num);
        formula = new ArrayList<StringBuilder>();
        answer = new ArrayList<StringBuilder>();
        //原始式子
        StringBuilder singleFormula;
        for(int i=0; formula.size()<num; i++) {
            formula.add(singleFormula = creat(maxNum));
            CalculateUtil.calculateFormula(extraCopy);
            //式子不符合规范(结果为负数),并且查重
            if(extraCopy.charAt(0)=='@' || CheckUtil.judgeRepeat(singleFormula,formulaLists,extraCopy,answer)) {
                formula.remove(formula.size()-1);
                continue;
            }
            answer.add(extraCopy);
        }
        int i=0;
        FileDao.storageFile(formula,"Exercises.txt");
        FileDao.storageFile(answer,"Answers.txt");
        System.out.println("生成时间: " + (System.currentTimeMillis()-beginTime));
    }

    public boolean numRange(int numerator, int denominator,int maxNum) {
        if((numerator/denominator)<maxNum) {
            return true;
        }else if((numerator/denominator)==maxNum) {
            if((numerator%denominator)==0) {
                return true;
            }
        }
        return false;
    }

}

ProcessUtil类定义了计算过程的相关方法:creatNum方法是将答案按规范生成出来;gcd方法可以求得两数的最大公因数;charFind方法用于存储指定字符的位序式子;changeNum方法是将数字字符串转为数字值;judge方法判断式子是否符合规范;change方法将字符串的操作数分子分母转成数字


public class ProcessUtil {

    public static StringBuilder creatNum(int numerator, int denominator) {
        StringBuilder num = new StringBuilder();
        int gcdNum = gcd(numerator, denominator);
        numerator /= gcdNum;
        denominator /= gcdNum;
        if (numerator >= denominator) {
            //分子大于等于分母
            if (numerator % denominator == 0) {
                //结果为整数
                num.append(numerator / denominator + " ");
            } else {
                //结果为带分数
                int interger = numerator / denominator;
                numerator = numerator - (interger * denominator);
                num.append(interger + "’" + numerator + "/" + denominator + " ");
            }
        } else {
            //分子小于分母
            if (numerator == 0) {
                //分子小于0
                num.append(0 + " ");
            } else {
                //其他情况
                num.append(numerator + "/" + denominator + " ");
            }
        }
        return num;
    }

    public static int gcd(int num01, int num02) {
        int num = 0;
        while (num02 != 0) {
            num = num01 % num02;
            num01 = num02;
            num02 = num;
        }
        return num01;
    }


    public static int[] charFind(String str, StringBuilder formula) {
        int[] indexs = new int[20];
        for (int i = 0; ; i++) {
            if (i == 0) {
                indexs[i] = formula.indexOf(str, 0);
                continue;
            }
            if (str.equals(" ") && (indexs[i - 1] == formula.length() - 1)) {
                break;
            }
            if (str.equals(" ") || str.equals("/")) {
                indexs[i] = formula.indexOf(str, indexs[i - 1] + 1);
            }
            if (str.equals("/") && (formula.length() - 1 - indexs[i] <= 4)) {
                break;
            }
        }
        return indexs;
    }


    public static int changeNum(StringBuilder formula, int fromIndex, int endIndex) {
        int num = -1;
        //根据数字的位数进行转换
        int sum = 0, temp;
        for (int i = 1; i < (endIndex - fromIndex); i++) {
            temp = (int) Math.pow((double) 10, (double) (i - 1));
            num = (int) (formula.charAt(endIndex - i) - 48) * temp;
            sum += num;
        }
        return sum;
    }

    public static boolean judge(int numerator1, int denominator1, int numerator2, int denominator2) {
        int numerator = numerator1 * denominator2 - numerator2 * denominator1;
        if (numerator < 0) {
            return false;
        }
        return true;
    }

    public static int[] change(StringBuilder extraCopy, int beginIndex) {
		int[] num = new int[3];
		int[] blanks = charFind(" ", extraCopy);//存储空格的位序,方便找到完整的操作数
		int indexBl = -1 ,indexBa ;
		indexBa = extraCopy.indexOf("/", beginIndex);//反斜杠的位置
		for(int i=0; i<blanks.length; i++) {
			if(blanks[i]==beginIndex) {//找到传入空格位序在blanks中的位置
				indexBl = i;
				break;
			}
		}
		num[0]=blanks[indexBl+1];//操作数后的空格位序
		num[1]=changeNum(extraCopy,beginIndex,indexBa);//分子
		num[2]=changeNum(extraCopy,indexBa,num[0]);//分母
		return num;
	}
}

CalculateUtil类定义了几种计算方法:add加法运算;minus减法运算;multiply乘法运算;divide除法运算;calculate对运算符号左右的两个数进行运算;calculateFormula计算式子

import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class CalculateUtil {

	private static final Logger logger = Logger.getLogger("CalculateUtil");

	public static StringBuilder add(int numerator1,int denominator1,int numerator2,int denominator2) {
		int numerator,denominator;
		StringBuilder result = new StringBuilder();
		numerator = numerator1*denominator2+numerator2*denominator1;
		denominator = denominator1 * denominator2;
		if(numerator!=0) {
			//化简分子分母(除以最大公因数)
			int gcdNum = ProcessUtil.gcd(numerator,denominator);
			numerator /= gcdNum;
			denominator /= gcdNum;
		}

		result.append(numerator+"/"+denominator);
		return result;
	}

	public static StringBuilder minus(int numerator1,int denominator1,int numerator2,int denominator2) {
		int numerator,denominator;
		StringBuilder result = new StringBuilder();

		numerator = numerator1*denominator2-numerator2*denominator1;
		denominator = denominator1*denominator2;
		//化简分子分母(除以最大公因数)
		if(numerator!=0) {
			int gcdNum = ProcessUtil.gcd(numerator,denominator);
			numerator /= gcdNum;
			denominator /= gcdNum;
		}
		result.append(numerator+"/"+denominator);
		return result;
	}

	public static StringBuilder multiply(int numerator1,int denominator1,int numerator2,int denominator2) {
		int numerator,denominator;
		StringBuilder result = new StringBuilder();
		//操作数有一个等于0的情况
		if(numerator1==0||numerator2==0) {
			result.append(0+"/"+1);
		}
		//操作数大于0的情况
		else {
			numerator = numerator1*numerator2;
			denominator = denominator1*denominator2;
			//化简分子分母(除以最大公因数)
			if(numerator!=0) {
				int gcdNum = ProcessUtil.gcd(numerator,denominator);
				numerator /= gcdNum;
				denominator /= gcdNum;
			}
			result.append(numerator+"/"+denominator);
		}
		return result;
	}

	public static StringBuilder divide(int numerator1,int denominator1,int numerator2,int denominator2) {
		int numerator,denominator;
		StringBuilder result = new StringBuilder();
		numerator = numerator1*denominator2;
		denominator = denominator1*numerator2;
		//化简分子分母(除以最大公因数)
		if(numerator!=0) {
			int gcdNum = ProcessUtil.gcd(numerator,denominator);
			numerator /= gcdNum;
			denominator /= gcdNum;
		}
		result.append(numerator+"/"+denominator);
		return result;
	}

	public static StringBuilder calculate(int index,StringBuilder extraCopy) {
		char sign = extraCopy.charAt(index);
		int beginIndex = 0, endIndex = -1;
		int[] datas = new int[3];
		for(int index1=0; ; beginIndex=index1) {
			//找到第一个操作数的开头空格
			index1 = extraCopy.indexOf(" ", index1+1);
			if(index1==(index-1)) {
				break;
			}
		}
		datas = ProcessUtil.change(extraCopy, beginIndex);
		int numerator1 = datas[1];
		int denominator1 = datas[2];
		datas = new int[3];
		datas = ProcessUtil.change(extraCopy, index+1);
		int numerator2 = datas[1];
		int denominator2 = datas[2];
		endIndex = datas[0];
		//删除数字部分
		extraCopy.delete(beginIndex+1,endIndex);
		//根据符号进行相应的运算
		switch(sign){
			case '+':
				extraCopy.insert(beginIndex+1, add(numerator1,denominator1,numerator2,denominator2));
				break;
			case '-':
				if(!ProcessUtil.judge(numerator1, denominator1, numerator2, denominator2)) {
					//识别答案是否为负数
					extraCopy.insert(0, "@ ");
					break;
				}
				else{
					extraCopy.insert(beginIndex+1, minus(numerator1,denominator1,numerator2,denominator2));
					break;
				}
			case '*':
				extraCopy.insert(beginIndex+1, multiply(numerator1,denominator1,numerator2,denominator2));
				break;
			case '÷':
				if(numerator2 == 0) {
					//识别答案是否为负数,是的话在开头插入@作为标识
					extraCopy.insert(0, "@ ");
					break;
				}
				else{
					extraCopy.insert(beginIndex+1, divide(numerator1,denominator1,numerator2,denominator2));
					break;
				}
			default: break;
		}
		return extraCopy;
	}

	public static StringBuilder calculateFormula(StringBuilder extraCopy) {
//		logger.info(extraCopy.toString());
		//记录符号的位序
		int index = -1;
		//计算式子
		Pattern pattern1 = Pattern.compile("[*]|[÷]");
		Matcher m1;
		while((m1 = pattern1.matcher(extraCopy)).find()) {
			index = m1.start();
			calculate(index, extraCopy);
			if(extraCopy.charAt(0)=='@') {
				break;
			}	
		}
		//如果式子正确,在进行加运算(从左到右)
		if(extraCopy.charAt(0)!='@') {
			Pattern pattern2 = Pattern.compile("[-]|[+]");
			Matcher m2;
			while((m2 = pattern2.matcher(extraCopy)).find()) {
				index = m2.start();
				calculate(index, extraCopy);
				if(extraCopy.charAt(0)=='@') {
					break;
				}	
			}
		}
		//如果运算结束后(式子正确),调整答案格式
		if(extraCopy.charAt(0)!='@') {
			int datas[] = new int[3];
			datas = ProcessUtil.change(extraCopy, 0);
			int numerator = datas[1];
			int denominator = datas[2];
			//将原存储内容清空
			extraCopy.setLength(0);
			//将答案换成标准格式
			extraCopy.append(ProcessUtil.creatNum(numerator, denominator));
		}
		return extraCopy;
	}
}

CheckUtil类可以校验式子,防止重复的题目:spiltStringBuilderToArray方法将式子拆分普通数组;spiltStringBuilderToList方法将式子拆分成List数组;spiltStringBuilderToOrderList方法将式子拆分成有序的 List 数组;judgeRepeat方法:判断内容是否有重复

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;


public class CheckUtil {
    
    public static String[] spiltStringBuilderToArray(StringBuilder stringBuilder){
        return stringBuilder.toString().split("\\s+");
    }

    public static List<String> spiltStringBuilderToList(StringBuilder stringBuilder){
        return Arrays.asList(spiltStringBuilderToArray(stringBuilder));
    }
    
    public static List<String> spiltStringBuilderToOrderList(StringBuilder stringBuilder){
        List<String> stringList = spiltStringBuilderToList(stringBuilder);
        Collections.sort(stringList);
        return stringList;
    }

    public static boolean judgeRepeat(StringBuilder formula, List<List<String>> lists,StringBuilder answer,List<StringBuilder> answerLists){
        List<String> formulaList = spiltStringBuilderToOrderList(formula);
        int i;
        for (i = 0;i<lists.size();i++){
            if(lists.get(i).equals(formulaList) && answer.toString().equals(answerLists.get(i).toString())){
                return true;
            }
        }
        lists.add(formulaList);
        return false;
    }

}

FileDao用于对文件导入和导出的封装:storageResult方法将结果存储到文件中;storageFile方法用于存储过程式子和答案;readFile方法是读取文件

import java.io.*;
import java.util.ArrayList;
import java.util.List;


public class FileDao {

    private static final String PATH = System.getProperty("user.dir");
    
    public static boolean storageResult(List<StringBuilder> list, String fileName)  {
        File file = new File(PATH + "\\" +fileName);
        FileOutputStream fileOutputStream = null;
        try {
            fileOutputStream = new FileOutputStream(file,false);
            String content = "";
            for (int i =0 ;i<list.size();i++){
                content = content + list.get(i).toString() + "\n";
            }
            fileOutputStream.write(content.getBytes());
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }finally {
            try {
                fileOutputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return true;
    }
    
    public static boolean storageFile(List<StringBuilder> list, String fileName)  {
        File file = new File(PATH + "\\" +fileName);
        FileOutputStream fileOutputStream = null;
        try {
            fileOutputStream = new FileOutputStream(file,false);
            String content = "";
            for (int i =0 ;i<list.size();i++){
                content = content + (i+1) + "、" + list.get(i).toString() + "\n";
            }
            fileOutputStream.write(content.getBytes());
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }finally {
            try {
                fileOutputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return true;
    }
    
    public static List<StringBuilder> readFile(File file) {
        List<StringBuilder> list = new ArrayList<>();
        FileInputStream fileInputStream = null;
        BufferedReader bufferedReader = null;
        //简单判断文件类型是否正确
        if (!file.exists() || !file.getName().contains("txt")){
            return null;
        }
        try {
            fileInputStream = new FileInputStream(file);
            bufferedReader = new BufferedReader(new InputStreamReader(fileInputStream));
            String raw;
            for (int i = 0; null != (raw = bufferedReader.readLine()); i++) {
                //文件内容是否有、,分情况输出
                if (raw.contains("、")) {
                    list.add(new StringBuilder(raw.substring(raw.indexOf("、") + 1, raw.length() - 1)));
                } else {
                    list.add(new StringBuilder(raw));
                }
            }
        }catch (IOException e){
            e.printStackTrace();
        }finally {
            try {
                fileInputStream.close();
                bufferedReader.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return list;
    }
}

Operator枚举类:对运算符相关信息进行封装,避免多次的 new 减低效能


public enum Operator {

   
    PLUS_SIGN("加号","+","+"),
    MINUS_SIGN("减号","-","-"),
    MULTIPLIED_SIGN("乘以号","*","*"),
    DIVISION_SIGN("除以号","÷","/"),
    EQUAL_SIGN("等于号","=","=");

    private String name;
    private String express;
    private String calculation;

    Operator(String name, String express, String calculation) {
        this.name = name;
        this.express = express;
        this.calculation = calculation;
    }

    public String getName() {
        return name;
    }

    public String getExpress() {
        return express;
    }

    public String getCalculation() {
        return calculation;
    }

}

四.测试运行

软件说明:
运行界面:
生成10000道题目:

验证答案:



同时支持题目导入功能,但需注意导入文件路径的填写。

路径填写错误则会有红框提示

五.性能分析

随机生成10000条运算式时:可以看到堆内存消耗在上升后趋于稳定

分析类的消耗,由于计算中大量使用字符串的加减,为减轻虚拟机的负担,故使用StringBuilder来进行大部分的字符串操作

六.经验总结

  由于我们两人之前都很少接触过团队合作,一开始都不知道要从哪里下手,于是找了个时间一起分析和分工。还有就是项目过程中也有一部分功能遗漏没能完成,缺陷很多。还需要慢慢地磨合才能完成任务,相比个人项目更需要交流和探讨。
  所以结对项目需要提前跟队友交流,对项目进行分析和分工。然后再开始着手,不然就可能有些地方遗漏,导致功能不稳定。还有就是两个人合作,能互相发现对方的编码缺点,同时能交流不错的编程思想,得到拓展。
posted @ 2021-10-25 22:05  清河h  阅读(14)  评论(0编辑  收藏  举报