首页 技术 正文
技术 2022年11月14日
0 收藏 393 点赞 4,213 浏览 6430 个字

1. GitHub 地址

本项目由 莫少政(3117004667)、余泽端(3117004679)结对完成。

项目 GitHub 地址:https://github.com/Yuzeduan/Arithmetic.git

2. PSP 表格

PSP2.1表格

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

3. 效能分析

初始时,生成一万道题目和答案所需时间约 4s 。

优化思路:由于 Java 中对象的初始化需要时间,因此在循环的时候,将变量的定义移到循环体外面,每次循环的时候复用已有的对象,从而避免了多次生成对象所消耗的时间。此外,相比于生成所有题目之后再一次性写入,便生成边写入文件,能避免一次性写入大量数据时文件读写缓冲区所耗费的时间。

优化后,生成一万道题目所花费时间约 0.9s 。

效能分析截图如下:

4. 设计思路

对题目进行分析之后,我们把项目总体分为两个大的功能模块,一个是生成题目以及生成答案,并写入到文件中,另一个是读取传入的参数名的文件题目以及答案,并进行题目的解答,校对答案,生成成绩,写入文件。

生成题目功能:首先需要判断参数,得到生成题目的个数,以及生成数的最大值,根据这两个参数来生成题目。第一步是生成操作符的个数,使用封装的随机数工具,获得操作符个数之后,生成相应数量的操作数,操作数有两种可能,一种是小数,一种是整数,因此需要随机生成两种类别,接着便是生成操作符,操作符有四种,同理也是这么操作,需要注意是,如果生成的是减号,为了使得计算过程中不能产生负数,因此需要生成数的时候,进行判断。最后一步,便是生成括号,括号插入到生成的题目中。

生成答案功能:首先需要判断有没有括号,如果有括号,应该先递归计算里面的子表达式,然后需要将真分数转化为小数,以及有个问题便是算术符号优先级不同,需要进行两次遍历,第一次只处理乘法和除法,第二次处理加法减法。

操作数应该封装成一个实体类,里面包含数值还有其前面的操作符,如果是第一个操作数,则其操作符属性为空。在生成题目和解析题目的时候,将每个操作数对象填充在一个容器中,进行统一管理。

5. 设计实现过程

  • Base 功能模块

    • FileUtil 类,其中提供 read,write 方法,进行文件的读取
    • RandomUtil 类,对随机数生成进行封装,构建通用的生成随机数工具
    • DecimalUtil 类,对小数,分数,真分数等进行转换
  • 逻辑模块
    • DataProvider 类:通过获取功能模块的数据,进行相应的逻辑处理,返回数据给调用的方案处理类
    • QuestionService 类:进行生成题目相关的业务逻辑,并执行文件读写操作
    • GradeService 类:进行题目和答案校对,生成分数的业务罗技,并执行读写操作

6. 关键代码说明

public String read() {
try {
String data = inputStream.readLine();
if (data != null) {
return data;
} else {
return null;
}
} catch (IOException e) {
e.printStackTrace();
return null;
}
}public void write(String data) {
outputStream.println(data);
}

进行文件的读取和写出,使用 Java 提供的 BufferedReade r和 PrintWriter 进行文件操作。

    public static float toFloat(String str) {
String[] strs = str.split("’");
if (strs.length == 1) {
String[] twoNum = strs[0].split("/");
return twoNum.length == 1 ? Float.valueOf(twoNum[0]) : (Float.valueOf(twoNum[0]) / Float.valueOf(twoNum[1]));
} else {
String[] twoNum = strs[1].split("/");
return Integer.valueOf(strs[0]) + Float.valueOf(twoNum[0]) / Float.valueOf(twoNum[1]);
}
}

读取文件时候,需要将真分数进行转化为小数,进行数值的处理,此处采用 String 类提供的 api 进行字符串处理,获取其数值。

public static String toStr(float dec) {
String[] strs = (dec + "").split("\\.");
if (strs[1].length() > 6) {
strs[1] = strs[1].substring(0, 6);
}
if (strs[1].contains("E")) return null;
int integer = Integer.valueOf(strs[0]);
int decimal = Integer.valueOf(strs[1]);
if (decimal == 0) return integer + "";
int mother = (int) Math.pow(10, strs[1].length());
int divisor = getMaxDivisor(decimal, mother);
return (integer > 0 ? integer + "’" : "") + decimal / divisor + "/" + mother / divisor;
}

将小数转换为分数,并且对过于小的数字,进行精度的截断,构建出真分数,并采用辗转相除法进行分数的化简。

public static String getAnswer(String question) {
question = question.replace(EQU, "");
if (question.contains("(")) {
int leftIndex = question.indexOf("(");
int rightIndex = question.indexOf(")");
String subQuestion = question.substring(leftIndex + 1, rightIndex);
if (subQuestion == null) {
System.out.println(1);
}
String subAnswer = getAnswer(subQuestion);
if (subAnswer == null) {
return null;
}
question = question.replace("(" + subQuestion + ")", subAnswer);
}
String[] preStrs = question.split(" ");
List<String> strs = new ArrayList<>();
if (preStrs.length == 1) {
return question;
}
for (int i = 1; i < preStrs.length; i += 2) {
if (preStrs[i].equals("×")) {
preStrs[i + 1] = DecimalUtil.toFloat(preStrs[i - 1]) * DecimalUtil.toFloat(preStrs[i + 1]) + "";
} else if (preStrs[i].equals("÷")) {
if (DecimalUtil.toFloat(preStrs[i + 1]) == 0) {
return null;
}
preStrs[i + 1] = DecimalUtil.toFloat(preStrs[i - 1]) / DecimalUtil.toFloat(preStrs[i + 1]) + "";
} else {
strs.add(preStrs[i - 1]);
strs.add(preStrs[i]);
}
if (i == preStrs.length - 2) {
strs.add(preStrs[i + 1]);
}
}
if (strs.size() == 1) {
return DecimalUtil.toStr(Float.valueOf(strs.get(0)));
}
for (int i = 1; i < strs.size(); i += 2) {
if (strs.get(i).equals("+")) {
strs.set(i + 1, DecimalUtil.toFloat(strs.get(i - 1)) + DecimalUtil.toFloat(strs.get(i + 1)) + "");
} else {
float temp = DecimalUtil.toFloat(strs.get(i - 1)) - DecimalUtil.toFloat(strs.get(i + 1));
if(temp < 0) return null;
strs.set(i + 1, temp + "");
}
if (i == strs.size() - 2) {
return DecimalUtil.toStr(Float.valueOf(strs.get(i + 1)));
}
}
return null;
}

在生成题目答案的实现中,首先是判断有没有括号的存在,有的话,进行递归调用该方法,算出值之后,替换掉表达式中的括号子表达式,接着进行第一次遍历,算出所有乘法和除法,填充进容器中,进行第二次遍历,算出加减法,值得注意的是,会在这个过程中判断类似9 - ( 5 + 7 )这种产生负数的情况,因为生成的时候,括号是最后生成的,此情况是会出现的,因此需要判断一下,如果出现该情况,就返回 Null 给上层,让其重新生成一道题目。

for (int question = 0; question < num; question++) {
Random random = new Random();
List<Number> nums = new ArrayList<>(); // 生成运算符数量
int operator = random.nextInt(3) + 1;
boolean isInt = false;
boolean hasParentheses = false;
String symbol = "";
float operateNum = -1;
for (int i = 0; i < operator + 1; i++) {
isInt = random.nextInt(2) == Constant.TYPE_INT;
symbol = i == 0 ? "" : SymbolList[random.nextInt(4)];
if (symbol.equals(SUB)) {
operateNum = isInt ? random.nextInt((int) (nums.get(i - 1).getNum()) + 1) : random.nextInt((int) ((nums.get(i - 1).getNum() + 0.01) * 100)) / 100.0f;
} else {
operateNum = isInt ? random.nextInt(max + 1) : random.nextInt((max + 1) * 100) / 100.0f;
}
if (symbol.equals(DIV)) {
operateNum = operateNum == 0 ? (isInt ? 1 : 0.01f) : operateNum;
}
nums.add(new Number(symbol, operateNum));
}
hasParentheses = random.nextInt(2) == 1;
int leftIndex = -1;
int rightIndex = -1;
if (hasParentheses) {
leftIndex = random.nextInt(operator);
rightIndex = random.nextInt(operator - leftIndex) + leftIndex + 1;
}
StringBuffer sb = new StringBuffer();
Number number;
for (int i = 0; i < nums.size(); i++) {
number = nums.get(i);
if (hasParentheses && i == leftIndex) {
sb.append(number.getSymbol())
.append("(")
.append(DecimalUtil.toStr(number.getNum()));
} else if (i == rightIndex) {
sb.append(number.getSymbol())
.append(DecimalUtil.toStr(number.getNum()))
.append(")");
} else {
sb.append(number.getSymbol()).append(DecimalUtil.toStr(number.getNum()));
}
}
sb.append(EQU);
String answer = DataProvider.getAnswer(sb.toString());
if (answer == null) {
question--;
continue;
}
questionFile.write(question + 1 + ". " + sb.toString());
answerFile.write(question + 1 + ". " + answer);
}

生成题目的时候,需要根据参数的题目数量进行判断循环次数,第一步是生成操作符的个数,使用封装的随机数工具,获得操作符个数之后,就进行生成操作数,操作数有两种可能,一种是小数,一种是整数,因此需要进行随机数生成两种类别,接着便是生成操作符,操作符有四种。将操作数填充进一个容器中,在最后转换成字符串时候,生成括号插入。

为了防止生成题目过多,导致写出文件出错,我们采用,在生成题目的开始,打开文件,每次生成一道题目,便写入,且算出答案。结束,关闭文件。

7. 测试运行

  • 生成题目及答案 ( 5 个测试用例)

1、 -n 5 -r 10

2、 -n 10 -r 20

3、 -n 1000 -r 30

4、 -n 10000 -r 50

5、 -n 1000000 -r 100

  • 批改题目 ( 5 个测试用例)

6、 5 道题目的批改

7、 10 道题目的批改(故意弄错 2 道题的答案)

8、 1000 道题目的批改(故意弄错 5 道题的答案)

9、 10000 道题目的批改

10、 1000000 道题目的批改

8. 项目总结

本次结对编程项目的结果,我们自认为较为成功,成功的原因主要是我们在编程之前一起讨论、进行设计,达成了功能实现上的共识,使得我们在后续的开发中心有灵犀,形成合力。

这次项目经历,让我们首次体验了这种“一边一个人写代码、一边另一个人 Code Review”的工作模式,非常新奇,也感到非常有趣。两个人讨论过、统一了思路之后,在结对编程中思路同步、相互提示并且传授编程思想,对技术进步非常有帮助。

莫少政:余泽端的闪光点在于技术非常厉害,在项目架构的设计上,分包、分层、容器等等很多工程化的规范的编程思想,使我受益匪浅。跟这样的大佬结对编程,能学到很多平时很难自己学到和摸索到的东西。

余泽端:莫少政的闪光点在于比较注重细节,有时候能留意到一些我疏忽的细节上的 bug 。

相关推荐
python开发_常用的python模块及安装方法
adodb:我们领导推荐的数据库连接组件bsddb3:BerkeleyDB的连接组件Cheetah-1.0:我比较喜欢这个版本的cheeta…
日期:2022-11-24 点赞:878 阅读:9,493
Educational Codeforces Round 11 C. Hard Process 二分
C. Hard Process题目连接:http://www.codeforces.com/contest/660/problem/CDes…
日期:2022-11-24 点赞:807 阅读:5,907
下载Ubuntn 17.04 内核源代码
zengkefu@server1:/usr/src$ uname -aLinux server1 4.10.0-19-generic #21…
日期:2022-11-24 点赞:569 阅读:6,740
可用Active Desktop Calendar V7.86 注册码序列号
可用Active Desktop Calendar V7.86 注册码序列号Name: www.greendown.cn Code: &nb…
日期:2022-11-24 点赞:733 阅读:6,495
Android调用系统相机、自定义相机、处理大图片
Android调用系统相机和自定义相机实例本博文主要是介绍了android上使用相机进行拍照并显示的两种方式,并且由于涉及到要把拍到的照片显…
日期:2022-11-24 点赞:512 阅读:8,133
Struts的使用
一、Struts2的获取  Struts的官方网站为:http://struts.apache.org/  下载完Struts2的jar包,…
日期:2022-11-24 点赞:671 阅读:5,297