结对编程(JAVA实现)
Posted 计科6班刘嘉媚
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了结对编程(JAVA实现)相关的知识,希望对你有一定的参考价值。
项目成员:黄思扬(3117004657)、刘嘉媚(3217004685)
一、github地址:https://github.com/Jasminejiamei/pairProgramming
二、PSP表格
PSP | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 60 | 40 |
· Estimate | · 估计这个任务需要多少时间 | 60 | 40 |
Development | 开发 | 1440 | 1505 |
· Analysis | · 需求分析 | 30 | 15 |
· Design Spec | · 生成设计文档 | 20 | 15 |
· Design Review | · 设计复审 | 30 | 15 |
· Coding Standard | · 代码规范 | 20 | 20 |
· Design | · 具体设计 | 80 | 80 |
· Coding | · 具体编码 | 900 | 980 |
· Code Review | · 代码复审 | 30 | 30 |
· Test | · 测试(自我测试,修改代码,提交修改) | 330 | 400 |
Reporting | 报告 | 130 | 100 |
· Test Report | · 测试报告 | 80 | 60 |
· Size Measurement | · 计算工作量 | 30 | 20 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 30 | 20 |
合计 | 1630 | 1695 |
三、效能分析
由于之前采用单线程执行,在文件IO流的处理上花费了不少的时间,包括代码上的执行存在部分冗余,代码上可以提高利用率。打开了线程池以后,多线程执行,大大提高了执行速度,在代码逻辑改进优化后,对大量生成题目的效果十分显著,由30s时间完成优化到2s:
四、设计过程
(一)流程图
视图设计过程:
生成题目设计过程:
判断对错设计过程:
(二)项目目录:
分包思路:
1)视图层:view 包含主页面与两个功能页面
2)实体类:po 包含题目存放类Deposit、ChildDeposit,分数处理类
3)逻辑处理层:service 处理题目生成等的逻辑、处理题目判错的逻辑
4)工具类:util 包含文件的读写功能
(三)总体实现思路
程序运行,进入主页面,点击选择进入相应功能页面(生成题目or判断对错),如果为生成题目,用户需要输入相关参数(题目数量、数的大小范围),视图层获取输入的数据,传入至逻辑层中进行处理,先判断输入是否有误,有误则终止程序,无误则调用题目生成的方法CreateAth;如果为判断对错,用户需要选择相应的文件(Exersises.txt和Answers.txt),视图层获取输入的数据,传入到逻辑层进行处理,判断输入无误后,调用题目判错的方法Judge。
题目生成的思路:题目要求生成的算术运算符少于3个,先随机生成一个运算符的个数,传入到CreateAth方法中,先生成一个根结点即算术运算符,然后随机生成在左右子树小于总运算符个数的运算符个数,同时生成运算符,当生成运算符个数为0,则生成叶子结点即运算操作数,左右子树也是重复上述过程。把生成的算式放在一个动态数组里面,每当生成一个算式,就去检查里面是否包含这个算式,如果重复就去掉,达到查重的目的。
判断对错的思路:使用readfile读取Exersises.txt文件内容,使用正则表达式分割开题目的序号和算式,算式是中缀表达式的表示方式,通过build方法把算式改为前缀表达式,如:1 + (( 2 + 3)* 4 ) – 5,转换成前缀则为- + 1 * + 2 3 4 5,计算其答案,读取Answers.txt的内容,使用正则表达式分割开题目的序号和答案,根据两个文件的序号,对比答案是否相同,如果相同则记录对的题目的数量,和题目序号,写出到Correction.txt文件。
(四)细节实现思路
1)如何保证基本的数值运算,确定参数的范围?
自然数的数值之间的运算可简单实现,但是自然数、真分数、带分数之间的运算之间的格式需要自己设计的,并且题目要求“如果存在形如e1÷ e2的子表达式,那么其结果应是真分数”,经过讨论之后,决定把所有数据统一当成分数来处理,整数的分母则为1,在运算的过程中把分子与分母独立出来分别操作加减乘除运算,到最后再进行约分等化简处理。
2)怎么生成算式并且查重?
生成的算式要求不能产生负数、生成的题目不能重复,且即任何两道题目不能通过有限次交换+和×左右的算术表达式变换为同一道题目,经过讨论,决定使用二叉树来实现,由于二叉树的运算次序是孩子结点的运算次序要优先于根结点的,所以使用非叶子节点存放运算符,叶子结点存放数值,可以解决前后符号之间的优先级别关系,解决括号的添加问题,当父母结点的算术符优先级高于右孩子结点的算术运算符时,左右都要加括号,当相等时,则右孩子树要加括号;出现了负数时,交换左右子树即可;此外,解决了查重的复杂度问题,开始的方案想采用遍历的方式来达到查重,现只需要判断两棵树是否相同即可。
五、程序关键代码
从页面获取到数据,进行处理:
package service; import java.util.HashMap; import java.util.Map; public class EntryJudge{ public boolean Entry(String[] args){ //向主页面返回的运行成功与否的标志 boolean tag = false; //判断用户输入是否正确 if(args.length == 0 || args.length % 2 != 0) { tag = false; return tag; } //取出参数 Map<String, String> params = checkParams(args); //执行相应处理 CreateAth opera = new CreateAth(params); Judge check = new Judge(params); if(params.containsKey("-e")&¶ms.containsKey("-a")){ check.Judge(); tag = true; return tag; } else if(params.containsKey("-n") || params.containsKey("-r") || params.containsKey("-d")) { opera.createAth(); tag = true; return tag; } return tag; } private Map<String, String> checkParams(String[] args) { Map<String, String> params = new HashMap<>(); for (int i = 0; i < args.length; i = i + 2) { params.put(args[i], args[i+1]); } return params; } }
题目生成逻辑处理:
package service; import com.sun.org.apache.xalan.internal.xsltc.compiler.util.StringStack; import po.Deposit; import po.Fraction; import util.FileUtil; import po.ChildDeposit; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Stack; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.TimeUnit; /** * 生成题目类 */ public class CreateAth{ private int maxNum = 100; //生成题目的整数最大值 private int denArea = 20; //分母的范围 private int maxCount = 10;//生成题目数量 private Deposit content; private static final String[] SYMBOLS = new String[]{ "+", "-", "x", "\\u00F7" }; /** * 生成随机题目,初始化,把主类中输入的参数内容调进来 */ public CreateAth(Map<String, String> params) { for (String str : params.keySet()) { if (str.equals("-n")) { maxCount = Integer.valueOf(params.get(str)); } else if (str.equals("-r")) { maxNum = Integer.valueOf(params.get(str)); } else if (str.equals("-d")) { denArea = Integer.valueOf(params.get(str)); } } } /** * 生成题目 */ private ExecutorService executor = Executors.newCachedThreadPool(); public void createAth() { StringBuilder exercises = new StringBuilder(); StringBuilder answers = new StringBuilder(); List<CreateAth> list = new ArrayList<>(); long start = System.currentTimeMillis(); for (int i = 1; i <= maxCount;) { CreateAth generate = new CreateAth(true); if (!list.contains(generate)){ String[] strs = generate.print().split("="); exercises.append(i).append(". ").append(strs[0]).append("\\n"); answers.append(i).append(".").append(strs[1]).append("\\n"); list.add(generate); i++; } } executor.execute(() -> FileUtil.writeFile(exercises.toString(), "Exercises.txt")); executor.execute(() -> FileUtil.writeFile(answers.toString(), "Answers.txt")); executor.shutdown(); long end = System.currentTimeMillis(); try { boolean loop = true; while (loop) { loop = !executor.awaitTermination(30, TimeUnit.SECONDS); //超时等待阻塞,直到线程池里所有任务结束 } //等待所有任务完成 System.out.println("生成的" + maxCount + "道题和答案存放在当前目录下的Exercises.txt和Answers.txt,耗时为:"+(end - start) + "ms"); } catch (InterruptedException e) { e.printStackTrace(); } } ——————以下是生成题目所调用到的方法——————————— /** * 生成组成题目随机数 * area:分母的范围 */ private int random(int area) { ThreadLocalRandom random = ThreadLocalRandom.current(); int x = random.nextInt(area); if (x == 0) x = 1; return x; } /** * ThreadLocalRandom类在多线程环境中生成随机数。 * nextBoolean() 方法用于从随机数生成器的序列返回下一个伪随机的,均匀分布的布尔值 */ private boolean randomBoolean() { ThreadLocalRandom random = ThreadLocalRandom.current(); return random.nextBoolean(); } /** * 把生成的每个数进行处理得出分子分母 */ private Fraction creator() { if (randomBoolean()) { return new Fraction((random(maxNum)), 1); } else { if (randomBoolean()) { int den = random(denArea); int mol = random(den * maxNum); return new Fraction(den, mol); } else { int den = random(denArea); return new Fraction(random(den), den); } } } /** * 单步计算 * @param symbol 符号 * @param left 左 * @param right 右 * @return 得出来结果后经过约分的分数 */ private Fraction calculate(String symbol, Fraction left, Fraction right) { switch (symbol) { case "+": return left.add(right); case "-": return left.subtract(right); case "x": return left.multiply(right); default: return left.divide(right); } } /** * 随机生成一道四则运算题目 * @param fractionNum 运算符个数 * @return 二叉树 */ private Deposit build(int fractionNum){ if(fractionNum == 0){ return new Deposit(creator(),null,null); } ThreadLocalRandom random = ThreadLocalRandom.current(); ChildDeposit node = new ChildDeposit(SYMBOLS [random.nextInt(4)],null, null); //左子树运算符数量 int left = random.nextInt(fractionNum); //右子树运算符数量 int right = fractionNum - left - 1; node.setLeft(build(left)); node.setRight(build(right)); Fraction value = calculate(node.getSymbol(),node.getLeft().getValue(),node.getRight().getValue()); //负数处理 if(value.Negative()){ //交换左右子树,就是交换两个减数的顺序 if (node != null) { Deposit swap = node.getLeft(); node.setLeft(node.getRight()); node.setRight(swap); } value = calculate(node.getSymbol(),node.getLeft().getValue(),node.getRight().getValue()); } node.setValue(value); return node; } /** * 获取表达式, * 打印题目与答案 */ private String print(){ return print(content) + " = " + content.getValue(); } private String print(Deposit node){ if (node == null){ return ""; } String frac = node.toString(); String left = print(node.getLeft()); if (node.getLeft() instanceof ChildDeposit && node instanceof ChildDeposit) { if (bracketsLeft(((ChildDeposit) node.getLeft()).getSymbol(), ((ChildDeposit) node).getSymbol())) { left = "(" + " " + left + " " + ")"; } } String right = print(node.getRight()); if (node.getRight() instanceof ChildDeposit && node instanceof ChildDeposit) { if (bracketsRight(((ChildDeposit) node.getRight()).getSymbol(), ((ChildDeposit) node).getSymbol())) { right = "(" + " " + right + " " + ")"; } } return left + frac + right; } /** * 比较两个符号谁优先级更高,子树的箱号优先级低要加括号,左括号or右括号 */ private boolean bracketsLeft(String left,String mid){ return (left.equals("+")|| left.equals("-")) && (mid.equals("x")||mid.equals("\\u00F7")); } private boolean bracketsRight(String right, String mid){ return (right.equals("+")|| right.equals("-")) && (mid.equals("x")||mid.equals("\\u00F7"))||(mid.equals("\\u00F7"))||(mid.equals("-")&&(mid.equals("+")|| mid.equals("-"))); } /** *生成一个题目,先调用下面的createAth方法来判断有没有生成一个用build方法生成的树 */ CreateAth(boolean isBuild){ if(isBuild){ ThreadLocalRandom random = ThreadLocalRandom.current(); int kind = random.nextInt(4); if (kind == 0) kind = 1; content = build(kind); while (content.getValue().Zero()){ content =build(kind); } } } /** * 查重 */ @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof CreateAth)) return false; CreateAth exercise = (CreateAth) o; return content.equals(exercise.content); } Fraction getResult() { return content.getValue(); } ——————以下是判断题目所要调用到的方法——————————— /** * 中缀表达式生成树,用栈的特点,把中缀表达式变成前缀表达式 * 在判错中调用 * @param exercise 中缀表达式 * @return 二叉树 */ Deposit build(String exercise) { String[] strs = exercise.trim().split(" "); //拿走标号 Stack<Deposit> depositStack = new Stack<>(); //结点栈 StringStack symbolStack = new StringStack(); //符号栈 //中缀表达式转换成前缀表达式,然后再用前序遍历生成数 for (int i = strs.length - 1; i >= 0; i--) { String str = strs[i]; if (!str.matches("[()+\\\\u00F7\\\\-x]")) { depositStack.push(new Deposit(new Fraction(str))); } else { //符号结点 while (!symbolStack.empty() && ((symbolStack.peekString().equals("x") || symbolStack.peekString().equals("\\u00F7")) && (str.equals("+") || str.equals("-")) || str.equals("("))) { String symbol = symbolStack.popString(); if (symbol.equals(")")) { break; } push(symbol, depositStack); } if (str.equals("(")) { continue; } symbolStack.pushString(str); } } while (!symbolStack.empty()) { push(symbolStack.popString(), depositStack); } this.content = depositStack.pop(); return content; } /** * 将符号压入节点栈且计算结果,仅在生成前缀表达式 */ private void push(String symbol, Stack<Deposit> nodeStack) { Deposit left = nodeStack.pop(); Deposit right = nodeStack.pop(); ChildDeposit node = new ChildDeposit(symbol, left, right); node.setValue(calculate(symbol, left.getValue(), right.getValue())); nodeStack.push(node); } }
题目判错处理:判断答案的正确性,并记录下来正确题目与错误题目序号,打印到Grade.txt
package service; import po.Fraction; import util.FileUtil; import java.util.ArrayList; import java.util.List; import java.util.Map; /** * 答案判错类 */ public class Judge{ private int trueNum; //正确数目 private int wrongNum; //错误数目 private String exerciseFileName; // 题目文件名 private String answerFileName; // 答案文件名 public Judge(Map<String, String> params) { for (String str : params.keySet()) { if (str.equals("-e")) { exerciseFileName = params.get(str); } else if (str.equals("-a")) { answerFileName = params.get(str); } } } /** * 判断错误 ,并把错误写入文件 */ public void Judge() { long start = System.currentTimeMillis(); List<String> correctNums = new ArrayList<>(); List<String> wrongNums = new ArrayList<>(); FileUtil.readFile((exercise, answer) -> { String[] strs1 = exercise.split("\\\\."); //匹配每一行 String[] strs2 = answer.split("\\\\."); if (strs1[0].equals(strs2[0])) { CreateAth exes = new CreateAth(false); exes.build(strs1[1].trim()); //去掉两端的空格后,将后缀表达式生成树变成前缀的, if (exes.getResult().equals(new Fraction(strs2[1].trim()))) { //答案两边都相等,继续执行下面的 correctNums.add(strs1[0]); trueNum++; } else { wrongNums.add(strs1[0]); wrongNum++; } } }, exerciseFileName, answerFileName); FileUtil.writeFile(printResult(correctNums, wrongNums), "Correction.txt"); long end = System.currentTimeMillis(); System.out.println("题目答案对错统计存在当前目录下的Correction.txt文件下,耗时为:" + (end - start) + "ms"); } private String printResult(List<String> correctNums, List<String> wrongNums) { StringBuilder builder = new StringBuilder(); builder.append("Correct: ").append(trueNum).append(" ("); for (int i = 0; i < correctNums.size(); i++) { if (i == correctNums.size() - 1) { builder.append(correctNums.get(i)); break; } builder.append(correctNums.get(i)).append(", "); } builder.append(")").append("\\n"); builder.append("Wrong: ").append(wrongNum).append(" ("); for (int i = 0; i < wrongNums.size(); i++) { if (i == wrongNums.size() - 1) { builder.append(wrongNums.get(i)); break; } builder.append(wrongNums.get(i)).append(", "); } builder.append(")").append("\\n"); return builder.toString(); } }
分数处理类:把所有随机生成的数都当成是分数处理,同时在些定义分数的四则运算方法
package po; /** * 1. 把所有随机生成的数都当成是分数处理(解决了自然整数,分数,带分数之间的差异) * 2. 定义了分数的四则运算类 */ public class Fraction{ private int mol; //分子 private int den; //分母 /** * 处理随机生成的数值(约分等),组合分数对象 */ public Fraction(int mol, int den) { this.mol = mol; this.den = den; if (den <= 0) { throw new RuntimeException("分数分母不能为0"); } //否则就进行约分 int mod = 1; int max = den > mol ? den : mol; for (int i = 1; i <= max; i++) { if (mol % i == 0 && den % i == 0) { mod = i; } } this.mol = mol / mod; this.den = den / mod; } /** * 处理随机生成的数值,这个用于分解分数对象(仅在判错中使用) */ public Fraction(String str) { int a = str.indexOf("\'"); int b = str.indexOf("/"); if (a != -1) { //取出数组,转换类型 int c = Integer.valueOf(str.substring(0, a)); den = Integer.valueOf(str.substring(b + 1)); mol = c * den + Integer.valueOf(str.substring(a + 1, b)); } else if (b != -1) { String[] sirs = str.split("/"); mol = Integer.valueOf(sirs[0]); den = Integer.valueOf(sirs[1]); } else { mol = Integer.valueOf(str); den = 1; } }/** * 定义加减乘除类,返回值类型(全都当成分数处理),由于要返回这个类的内容,所以方法前要加类名 */ public Fraction add(Fraction fraction) { return new Fraction(this.mol * fraction.den + this.den * fraction.mol, this.den * fraction.den); } public Fraction subtract(Fraction fraction) { return new Fraction(this.mol * fraction.den - this.den * fraction.mol, this.den * fraction.den); } public Fraction multiply(Fraction fraction) { return new Fraction(th以上是关于结对编程(JAVA实现)的主要内容,如果未能解决你的问题,请参考以下文章