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 。

【软件工程第三次作业】结对编程:四则运算( Java 实现)的更多相关文章

  1. 第三次作业-结对编程(wordcount)

    GIT地址 https://github.com/gentlemanzq/WordCount.git GIT用户名  gentlemanzq 结对伙伴博客地址 https://home.cnblogs ...

  2. 软件工程第三次作业-结对作业NO.1

    第一次结对作业 结对人员: 潘伟靖 170320077 张 松 170320079 方案分析 我们对所供的资料进行分析,如下: 从提供的资料可以看出,需要解决的问题以及满足的需求主要有两类目标用户,各 ...

  3. 结对编程四则运算--JAVA实现(徐静、林文敏)

    Github项目地址 项目相关要求 -n 参数控制生成题目的个数 (√) Myapp.exe -n 10 // 将生成10个题目 -r 参数控制题目中数值(自然数.真分数和真分数分母)的范围 (√) ...

  4. 个人第三次作业——结对编程 (姜玖林&于丁)

    博客要求 Github项目地址:https://github.com/zhibihuayue/PairProgramming 作业地址 : https://www.cnblogs.com/cheris ...

  5. 王译潇20162314 实验报告三plus结对编程四则运算第一阶段

    北京电子科技学院BESTI实验报告 课程:程序设计与数据结构 班级: 1623 姓名: 王译潇 学号:20162314 指导教师:娄佳鹏老师.王志强老师 实验日期:2017年5月12号 实验密级: 非 ...

  6. 20175305张天钰Java结对编程四则运算(二)

    Java结对编程四则运算(二) 一.题目描述及要求 Git提交粒度不要太粗,建议一个文件/一个类/一个函数/一个功能/一个bug修复都进行提交,不能一天提交一次,更不能一周一次,参考Commit Me ...

  7. 20175226 2018-2019-2《java程序设计》结对编程-四则运算(第一周-阶段总结)

    结对编程-四则运算(第一周-阶段总结) 需求分析 实现一个四则运算程序,要求: 自动随机生成小学四则运算题目(加,减,乘,除) 支持整数.真分数且支持多项式 能够利用栈的思想,将中缀转换为后缀表达式 ...

  8. 20175305张天钰Java结对编程四则运算

    Java结对编程四则运算 一.题目描述:如何对表达式进行求值运算呢 1.中缀表达式与后缀表达式(娄老师讲解) 中缀表达式就是运算符号在运算数中间的表达式,比如1+2,顾名思义,后缀表达式就是运算符在运 ...

  9. 结对编程--四则运算(Java)萧英杰 夏浚杰

    结对编程--四则运算(Java)萧英杰 夏浚杰 Github项目地址 功能要求 题目:实现一个自动生成小学四则运算题目的命令行程序 使用 -n 参数控制生成题目的个数(实现) 使用 -r 参数控制题目 ...

  10. 结对编程--四则运算(Java)梅进鹏 欧思良

    结对编程--四则运算(Java)梅进鹏 欧思良 Github项目地址:https://github.com/MeiJinpen/Arithmetic 功能要求 题目:实现一个自动生成小学四则运算题目的 ...

随机推荐

  1. SLAM:

    十四讲: 传感器约束了外部环境 测到的通常都是一些间接的物理量而不是直接的位置数据 只能通过一些间接的手段,从这些数据推算自己的位置 好处是没有对环境提出任何要求 camera:单目.双目.深度 Mo ...

  2. python3对urllib和urllib2进行了重构

    python3对urllib和urllib2进行了重构,拆分成了urllib.request,urllib.response, urllib.parse, urllib.error等几个子模块,这样的 ...

  3. Node.js 入门篇

    Node.js 使用C++开发的. Node.js是一个事件驱动服务端JavaScript环境,只要能够安装相应的模块包,就可以开发出需要的服务端程序,如HTTP服务端程序.Socket程序等. No ...

  4. [LeetCode] 857. Minimum Cost to Hire K Workers 雇佣K名工人的最低成本

    There are N workers.  The i-th worker has a quality[i] and a minimum wage expectation wage[i]. Now w ...

  5. Spring Cloud Gateway 结合配置中心限流

    前言 上篇文章我讲过复杂的限流场景可以通过扩展RedisRateLimiter来实现自己的限流策略. 假设你领导给你安排了一个任务,具体需求如下: 针对具体的接口做限流 不同接口限流的力度可以不同 可 ...

  6. springcloud2.x之management.security.enabled=false报错处理

    1. springcloud1.5.x的消息总线配置是 # RabbitMq的地址.端口,用户名.密码 spring.rabbitmq.host=localhost spring.rabbitmq.p ...

  7. java --后缀符号

    public class Sample { public static void main(String[] args) { , num2 = ; num1--; System.out.println ...

  8. 使用go-mysql-elasticsearch同步mysql数据库信息到ElasticSearch

    本文介绍如何使用go-mysql-elasticsearch同步mysql数据库信息到ElasticSearch. 1.go-mysql-elasticsearch简介 go-mysql-elasti ...

  9. oracle--sqlplus格式化输出

    01,日期格式化输出 SQL> alter session set NLS_DATE_FORMAT='YYYY-MM-DD HH24:mi:ss'; SQL> select sysdate ...

  10. JVM系列之六:内存溢出、内存泄漏 和 栈溢出

    1. OOM && SOF OutOfMemoryError异常: 除了程序计数器外,虚拟机内存的其他几个运行时区域都有发生OutOfMemoryError(OOM)异常的可能, 内存 ...