结对编程--四则运算(Java)梅进鹏 欧思良
Github项目地址:https://github.com/MeiJinpen/Arithmetic
功能要求
题目:实现一个自动生成小学四则运算题目的命令行程序
功能(已全部实现)
- 使用 -n 参数控制生成题目的个数
- 使用 -r 参数控制题目中数值(自然数、真分数和真分数分母)的范围(此处感觉一个参数不太合理,因此改用 -r 参数控制自然数、带分数整数部分的范围,又额外添加了 -d 参数控制分数分母的范围,增加可行性)
- 生成的题目中计算过程不能产生负数,也就是说算术表达式中如果存在形如e1 − e2的子表达式,那么e1 ≥ e2
- 生成的题目中如果存在形如e1 ÷ e2的子表达式,那么其结果应是真分数
- 每道题目中出现的运算符个数不超过3个
- 程序一次运行生成的题目不能重复,即任何两道题目不能通过有限次交换+和×左右的算术表达式变换为同一道题目
- 在生成题目的同时,计算出所有题目的答案,并存入执行程序的当前目录下的Answers.txt
- 程序应能支持一万道题目的生成
- 程序支持对给定的题目文件和答案文件,判定答案中的对错并进行数量统计,统计结果输出到文件Grade.txt
设计实现
1. 数值生成
题目要求生成的算式中包含整数,带分数(如 2 ‘3/5 )以及真分数 (如 3/5 ),考虑到整数是特殊的分数,所以随机生成的数值全部先当作分数处理,只需要创建一个Fraction分数类,定义 int 类型成员变量分子分母s , m 即可( 当然,m不能为0 ) 程序默认随机生成整数,真分数,带分数的数量比例为2:1:1 。对于负数的处理,把它当作一个环节放在了算式生成的算法里,这里生成的都是正数
然后在Fraction内部生成一个handle方法,用于分数约分,假分数化带分数,整数等规范化处理,同时简单定义分数类的加减乘除运算方法用于数值运算,其中规定减法的结果不为负数,除法的除数不为0
2. 算式生成
在算式括号的处理和添加,算式的查重处理,以及给定表达式的求值计算等方面,经过长时间的讨论和尝试,最终还是选择了采用二叉树实现,用非叶子节点存放运算符,叶子节点存放数值,二叉树实现的优势在于:
运算的次序简单明了,孩子结点的运算次序要优先于根结点,单这一特点就能避免很多问题
对给定字符串表达式求值,将中缀表达式转为前,后缀表达式几乎是不可避免的,这时将前,后缀表达式生成树(我们这里用的是前缀),计算结果时会更加高效和统一
采用二叉树实现,算式的查重就只需要判断树是否相同就可以了,并且,运算出现负数时,只需交换左右子树即可
在这里就上述第一点展开一下(也是当时遇到的一个问题),对于生成表达式时括号的添加情况,当父母结点的算术符优先级高于右孩子结点算术符的时候,左右孩子树的数值运算都要加括号(下图 1);而当优先级相等,且同为除法或减法时,右孩子树的数值运算要加括号(如下图2 ,3 中 ,生成算术表达式的时候,a 与 b 的运算需要用括号括起来),而由于乘法和加法满足交换律,所以不需要考虑
( a , b 不一定要求是叶子节点)
完成算式生成这一块的功能,需要创建Node结点类用于生成二叉树,Exercise题目类用于生成题目,再在Operation操作类里添加详细的生成,查重,判错等方法
3. 其他
- 程序所用到的常量EXERCISE_NUMBER ,EXERCISE_NUM_MAX ,EXERCISE_DER_MAX等都封装在常量类里
- 文件读写操作均封装在FileUtil类
- Main主类里存放main方法,内部的静态方法checkParams用于接收和命令判断
代码说明
分数类Fraction
其中 Fraction(String str)
,将数值字符串转具体分数实例的构造方法;String toString()
用于将分数实例用字符串的形式输出;handle()
用于约分等数值处理
/**
* 分数属性类(包括整数)
*/
public class Fraction {
private int s; //分子
private int m; //分母
/**
* 数值分解成分数对象
* @param str 表达式
*/
public Fraction(String str) {
int a = str.indexOf("'");
int b = str.indexOf("/");
if(a != -1) {
int z = Integer.valueOf(str.substring(0, a));
m = Integer.valueOf(str.substring(b + 1));
s = z * m + Integer.valueOf(str.substring(a + 1, b));
} else if(b != -1) {
String[] strs = str.split("/");
s = Integer.valueOf(strs[0]);
m = Integer.valueOf(strs[1]);
} else {
m = 1;
s = Integer.valueOf(str);
}
}
/**
* 通过分子分母组合成分数对象
* @param s 分子
* @param m 分母
*/
public Fraction(int s, int m) {
this.s = s;
this.m = m;
handle();
}
/**
* 约分
*/
private void handle() {
int mod = 1;
int max = s > m ? s : m;
for (int i = 1; i <= max; i++) {
if(s % i == 0 && m % i == 0) {
mod = i;
}
}
this.s = s / mod;
this.m = m / mod;
}
public boolean isZero() {
return m == 0;
}
public Fraction add(Fraction fraction) {
return new Fraction(this.s * fraction.m + this.m * fraction.s, this.m * fraction.m);
}
public Fraction multiply(Fraction fraction) {
return new Fraction(this.s * fraction.s, this.m * fraction.m);
}
public Fraction subtract(Fraction fraction) {
return new Fraction(this.s * fraction.m - this.m * fraction.s, this.m * fraction.m);
}
public Fraction divide(Fraction fraction) {
return new Fraction(this.s * fraction.m, this.m * fraction.s);
}
public boolean isNegative() {
return s < 0;
}
@Override
public String toString() {
if(m == 1) {
return String.valueOf(s);
} else {
int z = 0;
if(m != 0 && s > m) {
z = s / m;
}
if(z == 0) {
return String.valueOf(s) + "/" + String.valueOf(m);
} else {
return String.valueOf(z) + "'" + String.valueOf(s % m) + "/" + String.valueOf(m);
}
}
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Fraction)) return false;
Fraction fraction = (Fraction) o;
return s == fraction.s &&
m == fraction.m;
}
}
题目类Exercise
构造方法Exercise
中定义了参数isAuto
,判断是否随机生成题目Node类的 build()
方法用于生成题目实例对应的二叉树,减法结果的非负规范,以及是否添加括号统一都在这里处理;出现负数,就交换value
值小于0的结点的左右孩子树
/**
* 四则运算一道题目主要实现类
*/
public class Exercise {
private Operation operation;
private Node root;
public Exercise(Operation operation, boolean isAuto) {
this.operation = operation;
if(isAuto) {
ThreadLocalRandom random = ThreadLocalRandom.current();
int kind = random.nextInt(4);
if (kind == 0) kind = 1;
root = build(kind);
while (root.getValue().isZero()) {
root = build(kind);
}
}
}
public Fraction getResult() {
return root.getValue();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Exercise)) return false;
Exercise exercise = (Exercise) o;
return root.equals(exercise.root);
}
@Override
public int hashCode() {
return root.hashCode();
}
/**
* 随机生成一道四则运算题目
*
* @param num 运算符个数
* @return 二叉树
*/
public Node build(int num) {
if (num == 0) {
return new Node(createFraction(), null, null);
}
ThreadLocalRandom random = ThreadLocalRandom.current();
SymbolNode node = new SymbolNode(Constant.SYMBOLS[random.nextInt(4)], null, null);
int left = random.nextInt(num); //左子树运算符数量
int right = num - left - 1; //右子树运算符数量
node.setLeft(build(left));
node.setRight(build(right));
Fraction value = calculate(node.getSymbol(), node.getLeft().getValue(), node.getRight().getValue());
if (value.isNegative()) {
swapNode(node);
value = calculate(node.getSymbol(), node.getLeft().getValue(), node.getRight().getValue());
}
node.setValue(value);
return node;
}
/**
* 打印题目和答案,例如:“( 1 + 2 ) x 3 = 9”
*/
public String print() {
return print(root) + " = " + root.getValue();
}
/**
* 获取表达式,例如“( 1 + 2 ) x 3”
*/
public String print(Node node) {
if (node == null) {
return "";
}
String mid = node.toString();
String left = print(node.getLeft());
if (node.getLeft() instanceof SymbolNode && node instanceof SymbolNode) {
if (isNeedBracketsLeft(((SymbolNode) node.getLeft()).getSymbol(), ((SymbolNode) node).getSymbol())) {
left = Constant.LEFT_BRACKETS + " " + left + " " + Constant.RIGHT_BRACKETS;
}
}
String right = print(node.getRight());
if (node.getRight() instanceof SymbolNode && node instanceof SymbolNode) {
if (isNeedBracketsRight(((SymbolNode) node.getRight()).getSymbol(), ((SymbolNode) node).getSymbol())) {
right = Constant.LEFT_BRACKETS + " " + right + " " + Constant.RIGHT_BRACKETS;
}
}
return left + mid + right;
}
//判断是否为加减法
private boolean isAddOrSub(String symbol) {
return symbol.equals(Constant.ADDITION) || symbol.equals(Constant.SUBTRACTION);
}
//判断是否为乘除法
private boolean isMulOrDiv(String symbol) {
return symbol.equals(Constant.MULTIPLICATION) || symbol.equals(Constant.DIVISION);
}
//判断是否为减法
private boolean isSubtract(String symbol) {
return symbol.equals(Constant.SUBTRACTION);
}
private boolean isDivide(String symbol) {
return symbol.equals(Constant.DIVISION);
}
/**
* 比较两个符号中谁优先级最高,由于子树的符号优先级低需要加括号
*/
private boolean isNeedBracketsLeft(String left, String mid) {
return isAddOrSub(left) && isMulOrDiv(mid);
}
/*当右边需要加括号时,可以分为几种情况,第一种中间为除号时,右边所有运算都需要加括号,
第二种中间为减号时,右边的加减法需要加括号,第三种优先级*/
private boolean isNeedBracketsRight(String right, String mid) {
return isAddOrSub(right) && isMulOrDiv(mid) || isDivide(mid) || isSubtract(mid) && isAddOrSub(mid);
}
/**
* 单步计算
*/
private Fraction calculate(String symbol, Fraction left, Fraction right) {
switch (symbol) {
case Constant.ADDITION:
return left.add(right);
case Constant.MULTIPLICATION:
return left.multiply(right);
case Constant.SUBTRACTION:
return left.subtract(right);
default:
return left.divide(right);
}
}
/**
* 交换左右子树
*/
private void swapNode(Node node) {
if (node != null) {
Node t = node.getLeft();
node.setLeft(node.getRight());
node.setRight(t);
}
}
/**
* 随机生成分数
*/
private Fraction createFraction() {
if (randomBoolean()) {
return new Fraction(random(operation.maxNum), 1);
} else {
if (randomBoolean()) {
int y = random(operation.derBound);
int x = random(y * operation.maxNum);
return new Fraction(x, y);
} else {
int y = random(operation.derBound);
return new Fraction(random(y), y);
}
}
}
}
另一个build(String)
方法将题目字符串生成二叉树,用于结果计算。利用栈的特点,先将中缀表达式转前缀表达式,再计算算式的结果
/**
* 中缀表达式生成树
* @param exercise 中缀表达式
* @return 二叉树
*/
public Node build(String exercise) {
String[] strs = exercise.trim().split(" ");
Stack<Node> nodeStack = new Stack<>();
StringStack symbolStack = new StringStack();
//中缀表达式转换成前缀表达式,然后再用前序遍历生成数
for (int i = strs.length - 1; i >= 0; i--) {
String str = strs[i];
if (!str.matches("[()+\\u00F7\\-x]")) {
nodeStack.push(new Node(new Fraction(str)));
} else {
while (!symbolStack.empty() && (isMulOrDiv(symbolStack.peekString()) && isAddOrSub(str)
|| str.equals(Constant.LEFT_BRACKETS))) {
String symbol = symbolStack.popString();
if(symbol.equals(Constant.RIGHT_BRACKETS)) {
break;
}
push(symbol, nodeStack);
}
if(str.equals(Constant.LEFT_BRACKETS)) {
continue;
}
symbolStack.pushString(str);
}
}
while (!symbolStack.empty()) {
push(symbolStack.popString(), nodeStack);
}
this.root = nodeStack.pop();
return root;
}
/**
* 将符号压入节点栈且计算结果
*/
private void push(String symbol, Stack<Node> nodeStack) {
Node left = nodeStack.pop();
Node right = nodeStack.pop();
SymbolNode node = new SymbolNode(symbol, left, right);
node.setValue(calculate(symbol, left.getValue(), right.getValue()));
nodeStack.push(node);
}
还写了几个随机算法用于生成合适的随机数
private int random(int factor) {
ThreadLocalRandom random = ThreadLocalRandom.current();
int x = random.nextInt(factor);
if (x == 0) x = 1;
return x;
}
private boolean randomBoolean() {
ThreadLocalRandom random = ThreadLocalRandom.current();
return random.nextBoolean();
}
题目存放实现类Node 及其子类
定义value
表示结点的值(如果结点是数值结点则为本身,如果是符号则是子树运算后的结果),left
,right
指向左,右孩子,然后是一些简单的功能方法
/**
* 记录每个节点的信息
*/
public class Node {
private Fraction value; //如果节点是数值则为本身,如果是符号则是运算后的结果
private Node left;
private Node right;
public Node(Fraction value, Node left, Node right) {
this.value = value;
this.left = left;
this.right = right;
}
public Node(Fraction value) {
this.value = value;
}
.............
@Override
public String toString() {
return value.toString();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Node)) return false;
Node node = (Node) o;
return Objects.equals(value, node.value) &&
Objects.equals(left, node.left) &&
Objects.equals(right, node.right);
}
@Override
public int hashCode() {
return Objects.hash(value, left, right);
}
}
子类SymbolNode
用于记录符号结点,重写equals
方法,用于判断两棵树是否相同
/**
* 记录符号的节点
*/
public class SymbolNode extends Node {
private String symbol;
public SymbolNode(String symbol, Node left, Node right) {
super(null, left, right);
this.symbol = symbol;
}
...........
@Override
public String toString() {
return " " + symbol + " ";
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof SymbolNode)) return false;
SymbolNode that = (SymbolNode) o;
boolean flag = this.symbol != null && symbol.equals(that.symbol);
if(!flag) return false;
boolean left = this.getLeft() != null && getLeft().equals(that.getLeft());
boolean right = this.getRight() != null && getRight().equals(that.getRight());
//左右子树相同
if(left && right) {
return true;
}
if(left ^ right) {
return false;
}
//如果是加法或乘法由于满足交换律所以要判断
if(this.symbol.equals(Constant.ADDITION) || this.symbol.equals(Constant.MULTIPLICATION)) {
left = this.getLeft() != null && getLeft().equals(that.getRight());
right = this.getRight() != null && getRight().equals(that.getLeft());
}
return left && right;
}
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), symbol);
}
}
题目操作类Operation
Operation(Map<String, String> params)
匹配功能,拿到参数;同时初始化线程池,同时写两份文件,提高读写效率
/**
* 四则运算题目操作类
*/
public class Operation {
int maxCount = 100; //生成题目数量,默认100条
int maxNum = 100; //生成题目的数值最大值,默认为100
int derBound = 20; //分母的范围,默认为20
String exerciseFileName; // 题目文件名
String answerFileName; // 答案文件名
int correctCount; //正确数目
int wrongCount; //错误数目
//初始化
public Operation(Map<String, String> params) {
for (String str : params.keySet()) {
if (str.equals(Constant.EXERCISE_NUMBER)) {
maxCount = Integer.valueOf(params.get(str));
} else if (str.equals(Constant.EXERCISE_NUM_MAX)) {
maxNum = Integer.valueOf(params.get(str));
} else if (str.equals(Constant.EXERCISE_FILE_NAME)) {
exerciseFileName = params.get(str);
} else if (str.equals(Constant.ANSWER_FILE_NAME)) {
answerFileName = params.get(str);
} else if (str.equals(Constant.EXERCISE_DER_MAX)) {
derBound = Integer.valueOf(params.get(str));
}
}
}
private ExecutorService executor = Executors.newCachedThreadPool();
public void generateExercisesAnswers() {...}
public void checkExercises() {...}
private String printResult(List<String> correctNums, List<String> wrongNums) {...}
}
generateExercisesAnswers()
生成随机题目并查重,将已生成的题目(已判定无重复)加入题目链表list
里,调用contains
方法检测新生成的题目是否已重复直到生成给定数目的算式,并显示生成耗时
/**
* 生成随机题目
*/
public void generateExercisesAnswers() {
StringBuilder exercises = new StringBuilder();
StringBuilder answers = new StringBuilder();
List<Exercise> list = new ArrayList<>();
long start = System.currentTimeMillis();
for (int i = 1; i <= maxCount;) {
Exercise exercise = new Exercise(this, true);
if (!list.contains(exercise)) {
String[] strs = exercise.print().split("=");
exercises.append(i).append(". ").append(strs[0]).append("\n");
answers.append(i).append(".").append(strs[1]).append("\n");
list.add(exercise);
i++;
}
long end = System.currentTimeMillis();
if(end - start > 10000) {
throw new RuntimeException("生成题目时间过长,可能传入参数范围过大或过小,请重试");
}
}
executor.execute(() -> FileUtil.writeFile(exercises.toString(), Constant.EXERCISE_FILE_DEFAULT));
executor.execute(() -> FileUtil.writeFile(answers.toString(), Constant.ANSWER_FILE_DEFAULT));
//开启线程池执行任务后,关闭线程池释放资源
executor.shutdown();
try {
boolean loop = true;
while (loop) {
loop = !executor.awaitTermination(30, TimeUnit.SECONDS); //超时等待阻塞,直到线程池里所有任务结束
} //等待所有任务完成
long end = System.currentTimeMillis();
System.out.println("生成的" + maxCount + "道题和答案存放在当前目录下的Exercises.txt和Answers.txt,耗时为" + (end - start) + "ms");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void checkExercises()
结果判错,同时显示判错耗时
public void checkExercises() {
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])) {
Exercise exes = new Exercise(this, false);
exes.build(strs1[1].trim());
if(exes.getResult().equals(new Fraction(strs2[1].trim()))) {
correctNums.add(strs1[0]);
correctCount++;
} else {
wrongNums.add(strs1[0]);
wrongCount++;
}
}
}, exerciseFileName, answerFileName);
FileUtil.writeFile(printResult(correctNums, wrongNums), Constant.GRADE_FILE_DEFAULT);
long end = System.currentTimeMillis();
System.out.println("题目答案对错统计存在当前目录下的Grade.txt文件下,耗时为:" + (end - start) + "ms");
}
private String printResult()
将检错结果写入文件
private String printResult(List<String> correctNums, List<String> wrongNums) {
StringBuilder builder = new StringBuilder();
builder.append("Correct: ").append(correctCount).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(wrongCount).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();
}
其他
Constant常量类
public class Constant {
//参数
public static final String EXERCISE_NUMBER = "-n"; //生成的题目数量
public static final String EXERCISE_NUM_MAX = "-r"; //运算的数值大小, 2以上
public static final String EXERCISE_DER_MAX = "-d"; //运算分数的分母范围
public static final String EXERCISE_FILE_NAME = "-e"; //指定的题目文件名
public static final String ANSWER_FILE_NAME = "-a"; //指定的答案文件名
//默认生成的文件名
public static final String ANSWER_FILE_DEFAULT = "Answers.txt";
public static final String EXERCISE_FILE_DEFAULT = "Exercises.txt";
public static final String GRADE_FILE_DEFAULT = "Grade.txt";
public static final String ADDITION = "+";
public static final String SUBTRACTION = "-";
public static final String MULTIPLICATION= "x";
public static final String DIVISION = "\u00F7";
public static final String LEFT_BRACKETS = "(";
public static final String RIGHT_BRACKETS = ")";
public static final String[] SYMBOLS = {
ADDITION, SUBTRACTION, MULTIPLICATION, DIVISION
};
}
FileUtil文件类
public class FileUtil {
/**
* 写入文件中
* @param content 写入内容
* @param fileName 写入文件名
*/
public static void writeFile(String content, String fileName) {
File file = new File(fileName);
try (BufferedWriter bw = new BufferedWriter(new FileWriter(file))){
if(!file.exists()){
file.createNewFile();
}
bw.write(content);
bw.flush();
} catch (IOException e) {
System.out.println("文件操作失败...");
}
}
/**
* 读文件内容
* @param callBack 回调接口,分别处理每一行
* @param exerciseFileName 题目文件
* @param answerFileName 答案文件
*/
public static void readFile(ReaderCallBack callBack, String exerciseFileName, String answerFileName) {
File exerciseFile = new File(exerciseFileName);
File answerFile = new File(answerFileName);
if(!exerciseFile.exists() || !answerFile.exists()) {
System.out.println("文件不存在,请重试");
return;
}
try (BufferedReader br1 = new BufferedReader(new FileReader(exerciseFileName));
BufferedReader br2 = new BufferedReader(new FileReader(answerFileName))) {
String line1, line2;
while ((line1 = br1.readLine()) != null && (line2 = br2.readLine()) != null) {
callBack.deal(line1, line2);
}
} catch (IOException e) {
System.out.println("读取文件失败...");
}
}
public interface ReaderCallBack {
void deal(String exercise, String answer) throws IOException;
}
}
Main主类
public class Main {
public static void main(String[] args) {
Map<String, String> params = checkParams(args);
Operation operation = new Operation(params);
if(params.containsKey("-e") && params.containsKey("-a")) {
operation.checkExercises();
} else if(params.containsKey("-n") || params.containsKey("-r") || params.containsKey("-d")) {
operation.generateExercisesAnswers();
} else {
System.out.println("参数输入错误,请重试\n" + "必须输入参数:\n" + "-n: 生成题目的个数\n" +
"-r: 生成题目的数值大小\n" + "-e <exercisefile>.txt -a <answerfile>.txt: " +
"对给定的题目文件和答案文件,判定答案中的对错并进行数量统计");
}
}
private static Map<String, String> checkParams(String... args) {
Map<String, String> params = new HashMap<>();
if(args.length == 0) {
throw new RuntimeException("必须输入参数:\n" + "-n: 生成题目的个数\n" +
"-r: 生成题目的数值大小\n" + "-e <exercisefile>.txt -a <answerfile>.txt: " +
"对给定的题目文件和答案文件,判定答案中的对错并进行数量统计");
} else {
if(args.length % 2 != 0) {
throw new RuntimeException("参数格式输入错误...");
}
for (int i = 0; i < args.length; i = i + 2) {
params.put(args[i], args[i+1]);
}
}
return params;
}
}
测试运行
生成10000道题目
部分题目和结果
9959. 3'1/4 - 4/7
9960. 9'7/8 - 1
9961. 11'1/9 + 9 x 1
9962. 19 + 3/8
9963. 17 - 17
9964. 3/5 ÷ ( 18 + 16 )
9965. 4 + 2/5
9966. 7'1/3 + 11
9967. ( 12 - 6 ) ÷ ( 14'7/9 + 14 )
9968. 18 - ( 11 - 5 + 1 )
9969. 11'3/4 ÷ ( 1 x 3 )
9959. 2'19/28
9960. 8'7/8
9961. 20'1/9
9962. 19'3/8
9963. 0
9964. 3/170
9965. 4'2/5
9966. 18'1/3
9967. 54/259
9968. 11
9969. 3'11/12
完整题目答案链接:
将序号为1 ,3,5,9996,9998,10000的答案改成错误值,运行 -e -a
Grade.txt:
代码覆盖率:
PSP
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 40 | 40 |
· Estimate | · 估计这个任务需要多少时间 | 40 | 40 |
Development | 开发 | 810 | 1240 |
· Analysis | · 需求分析 (包括学习新技术) | 60 | 120 |
· Design Spec | · 生成设计文档 | 30 | 30 |
· Design Review | · 设计复审 (和同事审核设计文档) | 20 | 50 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 20 | 20 |
· Design | · 具体设计 | 60 | 120 |
· Coding | · 具体编码 | 500 | 720 |
· Code Review | · 代码复审 | 60 | 60 |
· Test | · 测试(自我测试,修改代码,提交修改) | 60 | 120 |
Reporting | 报告 | 110 | 100 |
· Test Report | · 测试报告 | 30 | 40 |
· Size Measurement | · 计算工作量 | 20 | 20 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 60 | 40 |
合计 | 960 | 1380 |
项目小结
此次的结对编程,由梅进鹏同学负责前期编码,我主要负责后期的代码测试和问题修改,虽然是这样说,但是其实两个人每边都多少有所涉及。从一开始的讨论项目基本构思,到基础数值的定义和生成,再到采用二叉树存放算式,最后一步步完成项目,可以说这次的结对编程,通过两个人的讨论,使得项目的整体思路变得很清晰,编程也很有条理。事实证明,多讨论交流意见对编程的帮助很大,就如何查重这一点上,我起先是打算先检查结果,再一一比较算式的符号,而梅进鹏同学觉得这样效率不高,反复讨论后,最终选择用二叉树存放四则算式。而后的判错功能,起先我们都没有头绪,只好通过上网查阅资料,然后一起搞懂了一个栈的应用实例:中缀表达式转前缀表达式,可以说是收获颇多。而且,梅进鹏同学的编码经验很丰富,逻辑思维能力也很强,很多时候都是他在帮我讲解算法。