四则运算生成器
合作者:16计科5班 陈俊旭(3116004630) 16计科5班 张婷(3216004672)
1. 项目介绍
该项目需求为实现一个自动生成小学四则运算题目的命令行程序,并且可以通过命令行参数控制生成题目的个数还有控制题中数值的范围,并满足一些其它的需求。我们的结对项目完成的需求如下:
需求 | 是否实现 |
---|---|
使用 -n 参数控制生成题目的个数 | √ |
使用 -r 参数控制题目中数值(自然数、真分数和真分数分母)的范围 | √ |
生成的题目中计算过程不会产生负数 | √ |
形如 e1 ÷ e2 的子表达式结果为真分数 | √ |
每道题目中出现的运算符个数不超过3个 | √ |
程序一次运行生成的题目不能重复 | √ |
2. 耗时预计
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 90 | |
· Estimate | · 估计这个任务需要多少时间 | 90 | |
Development | 开发 | 1260 | |
· Analysis | · 需求分析 (包括学习新技术) | 180 | |
· Design Spec | · 生成设计文档 | 30 | |
· Design Review | · 设计复审 (和同事审核设计文档) | 120 | |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 30 | |
· Design | · 具体设计 | 240 | |
· Coding | · 具体编码 | 300 | |
· Code Review | · 代码复审 | 240 | |
· Test | · 测试(自我测试,修改代码,提交修改) | 120 | |
Reporting | 报告 | 150 | |
· Test Report | · 测试报告 | 60 | |
· Size Measurement | · 计算工作量 | 30 | |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 60 | |
合计 | 1500 |
(实际耗时见下面的 7.实际耗时)
3. 效能分析
以下是生成 100万道范围为 0 到 20 的效能分析
参数为:
经过多次测试,生成 100万道题目的平均用时为35s,我们选取其中的一次测试进行性能分析
首先是 CPU、内存以及 GC 等的使用情况:
其中我们主要研究内存和CPU的使用情况,可以看到生成这么多题目总计需要使用 1GB内存,而且CPU的利用率时高时低,经过分析CPU主要的计算用在表达式去重上;也就是每次生成一个表达式都需要与之前的所有表达式进行比较:
接着是各个类的实际使用情况,可以看到最多的是对字符串(转换成 char
数组)进行操作:
4. 设计实现过程
项目中定义的类和函数的功能如下:
com/laomi/bo/Expression.java
: 定义了每道表达式的属性,包括运算符、运算数、括号等等com/laomi/service/Calulator.java
:用于计算每道表达式的答案count()
:该函数用于获取具体的运算数,同时根据运算符号调用其它的各运算函数
com/laomi/service/ExerciseBook.java
:用于生成问卷和答案,同时提供批改问卷的功能generateFile()
:该函数用于生成问卷和答案文件checkAnswers()
:该函数用于批改问卷并生成分数
com/laomi/service/Producer.java
:真正生成表达式的地点,可以配置各种生成参数produce()
:统筹各生成函数的主函数
com/laomi/service/Fraction.java
: 实现小数转换分数,以及模拟分数的四则运算com/laomi/view/Main.java
:进行命令参数控制
(以上只是列举了部分函数的功能,具体的实现请看 5.代码说明)
下面是关键函数 produce()
的流程图
st=>start: 题目数量 n 和运算范围 r,
已生成表达式数量 i
e=>end: 返回所有符合条件的表达式集合
cond=>condition: i < n
nums=>operation: 生成运算数和运算符号
parenthesis=>operation: 生成括号
fraction=>operation: 转换为分数
answer=>operation: 计算表达式答案
st->cond
cond(yes)->nums->parenthesis->fraction->answer->e
cond(no)->e
5. 代码说明
Bo层
com/laomi/bo/Expression.java
:
public class Expression {
private Number[] nums; // 运算数(小数形式)
private String[] ultimateNumber; // 运算数(分数形式)
private char[] ops; // 运算符
private int len; // 运算数个数
private int[][] parenthesis; // 括号位置
private boolean isCorrect; // 表达式是否合法
private double answer; // 答案(小数形式)
private String ultimateAnswer; // 答案(分数形式)
// 此处省略 Get 和 Set 方法
/**
* 将表达式转化为一个数组
* @return 数组
*/
public List<String> zhangting() {
List<String> ting = new LinkedList<>();
Fraction.convertToFraction(this);
setNumsToString(Fraction.elements);
for (int i = 0; i < len; i++) {
int left = parenthesis[i][0];
int right = parenthesis[i][1];
while (left > 0) {
ting.add("(");
left--;
}
ting.add(numsToString[i] + "");
while (right > 0) {
ting.add(")");
right--;
}
if (i < len - 1) {
ting.add(ops[i] + "");
}
}
return ting;
}
//
public String printOrigin() {
StringBuilder s = new StringBuilder();
for (int i = 0; i < len; i++) {
int left = parenthesis[i][0];
int right = parenthesis[i][1];
while (left > 0) {
s.append("(");
left--;
}
s.append(nums[i]).append(" ");
while (right > 0) {
s = new StringBuilder(s.toString().trim());
s.append(") ");
right--;
}
if (i < len - 1) {
s.append(ops[i]).append(" ");
}
}
return s.toString().trim();
}
@Override
public String toString() {
StringBuilder s = new StringBuilder();
for (int i = 0; i < len; i++) {
while (parenthesis[i][0] > 0) {
s.append("(");
parenthesis[i][0]--;
}
// s.append(ultimateNumber[i]).append(" ");
s.append(numsToString[i]).append(" ");
while (parenthesis[i][1] > 0) {
s = new StringBuilder(s.toString().trim());
s.append(") ");
parenthesis[i][1]--;
}
if (i < len - 1) {
s.append(ops[i]).append(" ");
}
}
return s.toString();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Expression that = (Expression) o;
Number[] comThat;
if(len!=that.len)
{
return false;
}
Number[] thatnums = new Number[that.len];
Number[] thisnums = new Number[len];
for(int i=0;i<that.len;i++)
{
thatnums[i] = that.nums[i].doubleValue();
}
Arrays.sort(thatnums);
for(int i=0;i<len;i++)
{
thisnums[i] = nums[i].doubleValue();
}
Arrays.sort(thisnums);
return len == that.len &&
isCorrect == that.isCorrect &&
Double.compare(that.answer, answer) == 0 &&
Arrays.equals(thisnums,thatnums)&&
Objects.equals(ultimateAnswer, that.ultimateAnswer);
}
@Override
public int hashCode() {
int result = Objects.hash(len, isCorrect, answer, ultimateAnswer);
result = 31 * result + Arrays.hashCode(nums);
result = 31 * result + Arrays.hashCode(ultimateNumber);
result = 31 * result + Arrays.hashCode(ops);
return result;
}
}
Service层
com/laomi/service/Producer.java
:
public class Producer {
private static final char[] OPERATIONS = {'+', '-', 'x', '÷'};
/**
* 生成括号概率, 范围为 0~1, 数值越大生成括号概率越大
*/
private double parenthesisFactor = 0.7;
/**
* 生成分数概率, 范围为 0~1, 数字越大生成分数概率越大
*/
private double fractionFactor = 0.2;
/**
* 参与运算的数值的个数
*/
private int maxLen = 4;
/**
* 各个算数值的最大值(不包括)
*/
private int numberBound;
/**
* 生成表达式的数量
*/
private int amount;
/**
* 生成表达式
* @return 表达式集合
*/
public Set<Expression> produce() {
Set<Expression> expressions = new HashSet<>();
int count = 0;
while (count < amount) {
Expression e = new Expression(ThreadLocalRandom.current().nextInt(2, maxLen + 1));
for (int i = 0; i < e.getLen(); i++) {
// 生成运算数
e.getNums()[i] = getRandomNumber();
if (i < e.getLen() - 1) {
// 生成运算符号
e.getOps()[i] = getRandomOperation();
}
}
// 生成括号
polish(e, 0, e.getLen() - 1);
// 计算答案
String answer = Calculator.count(e.zhangting());
// 小数转换为分数
Fraction.convertToFraction(e);
if (answer == null || answer.contains("-")) {
e.setCorrect(false);
} else {
e.setUltimateAnswer(answer);
}
if (e.isCorrect() && !expressions.contains(e)) {
expressions.add(e);
count++;
}
}
return expressions;
}
/**
* 生成随机运算符
* @return 随即运算符
*/
private char getRandomOperation() {
return OPERATIONS[new Random().nextInt(OPERATIONS.length)];
}
/**
* 生成 0(包括 0)以上的随机数字
*
* @return 随机数字
*/
private Number getRandomNumber() {
if (new Random().nextDouble() < fractionFactor) {
double d = ThreadLocalRandom.current().nextDouble(0, numberBound);
d = BigDecimal.valueOf(d).setScale(2, RoundingMode.HALF_UP).doubleValue();
if (d == (int) d) {
return (int) d;
} else {
return d;
}
} else {
return new Random().nextInt(numberBound);
}
}
/**
* 递归生成括号
* @param e 表达式
* @param start 从哪里开始生成括号
* @param end 到哪里停止生成括号
*/
private void polish(Expression e, int start, int end) {
if (end > start && new Random().nextDouble() < parenthesisFactor) {
int middle = ThreadLocalRandom.current().nextInt(start, end);
// 避免在表达式最外层套括号
if (!(start == 0 && end == e.getLen() - 1)) {
if (new Random().nextDouble() < parenthesisFactor) {
e.getParenthesis()[start][0]++;
e.getParenthesis()[end][1]++;
}
}
polish(e, start, middle);
polish(e, middle + 1, end);
}
}
public double getParenthesisFactor() {
return parenthesisFactor;
}
public void setParenthesisFactor(double parenthesisFactor) {
this.parenthesisFactor = parenthesisFactor;
}
public double getFractionFactor() {
return fractionFactor;
}
public void setFractionFactor(double fractionFactor) {
this.fractionFactor = fractionFactor;
}
public int getMaxLen() {
return maxLen;
}
public void setMaxLen(int maxLen) {
this.maxLen = maxLen;
}
public int getNumberBound() {
return numberBound;
}
public void setNumberBound(int numberBound) {
this.numberBound = numberBound;
}
public int getAmount() {
return amount;
}
public void setAmount(int amount) {
this.amount = amount;
}
}
com/laomi/service/Calculator.java
:
public class Calculator {
/**
* 对表达式进行计算
* @param e 表达式
* @return 运算结果
*/
public static String count(List<String> e) {
Stack<String> stack = new Stack<>();
for (String arg : e) {
if (!")".equals(arg)) {
stack.push(arg);
} else {
List<String> subExpression = new LinkedList<>();
while (!stack.empty()) {
if ("(".equals(stack.peek())) {
stack.pop();
String result = countWithoutParenthesis(subExpression);
if (result == null) {
return null;
}
stack.push(result + "");
break;
} else {
subExpression.add(0, stack.pop());
}
}
}
}
String result;
if (stack.size() > 1) {
List<String> subExpression = new LinkedList<>();
while (!stack.empty()) {
subExpression.add(0, stack.pop());
}
result = countWithoutParenthesis(subExpression);
if (result == null) {
return null;
}
} else {
result = stack.pop();
}
return result;
}
/**
* 计算没有括号参与的情景, 类似 3 + 2 / 10
* @param e 表达式
* @return 运算结果
*/
private static String countWithoutParenthesis(List<String> e) {
List<String> exp = new CopyOnWriteArrayList<>(e);
int start = 0;
int index;
while ((index = getOperationIndex(exp, start, "x", "÷")) != -1) {
if( operation(exp, index) == -1) {
return null;
}
start = index - 1;
}
start = 0;
while ((index = getOperationIndex(exp, start, "+", "-")) != -1) {
if (operation(exp, index) == -1) {
return null;
}
start = index - 1;
}
return exp.get(0);
}
/**
* 获取表达式特定操作符的下标
* @param e 表达式
* @param start 从哪里开始获取
* @param ops 特定的操作符
* @return 下标
*/
private static int getOperationIndex(List<String> e, int start, String... ops) {
for (int i = start; i < e.size(); i++) {
String arg = e.get(i);
for (String op : ops) {
if (op.equals(arg)) {
return i;
}
}
}
return -1;
}
/**
* 根据操作符对操作数进行操纵
* @param e 表达式
* @param index 操作符下标
* @return 操作成功返回1, 失败返回-1
*/
private static int operation(List<String> e, int index) {
String op = e.get(index);
String pre = e.get(index-1);
String post = e.get(index+1);
// 从后往前删除
e.remove(index + 1);
e.remove(index);
e.remove(index - 1);
switch (op) {
case "x":
e.add(index - 1, Fraction.fractionMultiply(pre,post)+ "");
break;
case "÷":
if (Fraction.fractionCompare(pre,post).equals(">")) { // 避免产生假分数
return -1;
}
String divide = Fraction.fractionDivide(pre, post);
if (divide == null) { // 出现除以0的情况
return -1;
}
e.add(index - 1, divide);
break;
case "+":
e.add(index - 1, Fraction.fractionAdd(pre,post) + "");
break;
case "-":
if (Fraction.fractionCompare(pre,post).equals("<")) { // 避免产生负数
return -1;
}
e.add(index - 1, Fraction.fractionSubstract(pre,post) + "");
break;
default:
break;
}
return 1;
}
}
com/laomi/service/ExerciseBook.java
:
public class ExerciseBook {
/**
* 生成问卷和答案文本
* @param expressions 表达式列表
*/
public static void generateFile(List<Expression> expressions) {
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyyMMdd-HHmmss");
LocalDateTime now = LocalDateTime.now();
String current = dtf.format(now);
// 生成问卷
StringBuilder question = new StringBuilder();
//将表达式中的假分数化成真分数
for (int i = 0; i < expressions.size(); i++) {
Expression e = expressions.get(i);
for(int j=0;j<e.getLen();j++)
{
if(e.getNumsToString()[j].contains("/"))
{
String toTrue = Fraction.toRealFraction(e.getNumsToString()[j]);
if(toTrue!=null)
{
e.getNumsToString()[j] = toTrue;
}
}
}
question.append("" + (i + 1) + ". " + e + "=" + "\r\n");
}
// 生成答案
StringBuilder answer = new StringBuilder();
for (int i = 0; i < expressions.size(); i++) {
Expression e = expressions.get(i);
if(e.getUltimateAnswer().contains("/"))
{
String AnswerToTrue = Fraction.toRealFraction(e.getUltimateAnswer());
if(AnswerToTrue!=null)
{
e.setUltimateAnswer(AnswerToTrue);
}
}
answer.append("" + (i + 1) + ". " + e.getUltimateAnswer() + "\r\n");
}
try {
// 问卷写入文件
File file = new File(System.getProperty("user.dir") + "/" + "Exercises_" + current + ".txt");
PrintStream ps = new PrintStream(new FileOutputStream(file));
ps.append(question);
// 答案写入文件
file = new File(System.getProperty("user.dir") + "/" + "Answer_" + current + ".txt");
ps = new PrintStream(new FileOutputStream(file));
ps.append(answer);// 在已有的基础上添加字符串
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
/**
* 检查用户输入的答案是否正确并作出批改
* @param exerciseFile 问卷地址
* @param userAnswerPath 用户的答卷
*/
public static void checkAnswers(String exerciseFile, String userAnswerPath) {
List<String> Correct = new ArrayList<>();
List<String> False = new ArrayList<>();
File excersice = new File(exerciseFile);
String answer_timestamp = exerciseFile.substring(exerciseFile.lastIndexOf("_") + 1, exerciseFile.lastIndexOf("."));
File answer = new File(System.getProperty("user.dir") + "/" + "Answer_" + answer_timestamp + ".txt");
try {
FileReader readuser = new FileReader(new File(userAnswerPath));
FileReader readanswer = new FileReader(answer);
BufferedReader bufferuser = new BufferedReader(readuser);
BufferedReader bufferanswer = new BufferedReader(readanswer);
String s_answer = null;
String s_user = null;
while ((s_answer = bufferanswer.readLine()) != null && (s_user = bufferuser.readLine()) != null) {
if (s_answer.equals(s_user)) {
Correct.add(s_answer.substring(0, s_answer.lastIndexOf(".")));
} else {
False.add(s_answer.substring(0, s_answer.lastIndexOf(".")));
}
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
File Grade = new File(System.getProperty("user.dir") + "/" + "Grade" + ".txt");
StringBuilder s_grade = new StringBuilder("Correct: ");
s_grade.append(Correct.size() + "(");
for (int i = 0; i < Correct.size(); i++) {
s_grade.append(Correct.get(i) + ",");
}
s_grade.append(")" + "\n");
s_grade.append("Wrong: ");
s_grade.append(False.size() + "(");
for (int i = 0; i < False.size(); i++) {
s_grade.append(False.get(i) + ",");
}
s_grade.append(")" + "\n");
String tograde = s_grade.toString();
try {
PrintStream ps = new PrintStream(new FileOutputStream(Grade));
ps.append(tograde);// 在已有的基础上添加字符串
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}
com/laomi/service/Fraction.java
public class Fraction {
public static String[] elements;
//最大公约数
private static Integer gcd(Integer a, Integer b)
{
if(a==0){
return b;
}
return gcd(b%a,a);
}
private static void getGCD(Integer a, Integer b)
{
int gcd = gcd(a,b);
if(gcd!=0) {
a /= gcd(a,b);
b /= gcd(a,b);
}
}
//最小公倍数
private static Integer lcm(Integer a, Integer b)
{
if(gcd(a,b)!=0)
{
return a*b/gcd(a,b);
}
else
{
return 0;
}
}
/**
* 将随机生成的小数改为分数
* @param number 要改成分数的小数
* @return String
*/
public static String toFraction(double number)
{
int zhenshu = 0;
zhenshu = (int)Math.floor(number);
//System.out.println(zhenshu);
double decimal = number-zhenshu;
//System.out.println(decimal);
int cnt = 0;
Integer all = 0;
Integer base = 1;
String s = (BigDecimal.valueOf(decimal).toString());
int start = s.indexOf(".")+1;
int len = min(s.length()-start,start+1);
for(int i=start;i<=len;i++)
{
all = all+(Integer)(s.charAt(i)-'0')*base;
base = base*10;
}
String res = null;
Integer g = gcd(all,base);
if(g!=0) {
all /= g;
base /= g;
String frac = all + "/" + base;
if(all==0)
{
res = ""+zhenshu;
}
else {
res = fractionAdd(String.valueOf(zhenshu), frac);
}
}
return res;
}
/**
* 将分数改成真分数
* @param number 要改成分数的小数
* @return String
*/
public static String toRealFraction(String number)
{
String res = null;
if(number.contains("/"))
{
int top = Integer.parseInt(number.substring(0,number.indexOf("/")));
int down = Integer.parseInt(number.substring(number.indexOf("/")+1));
if(top>down)
{
int zhenshu = top/down;
int res_top = top-zhenshu*down;
int res_down = down;
if(res_top!=0)
{
int g = gcd(res_top,res_down);
res_top/=g;
res_down/=g;
res = zhenshu+"'"+res_top+"/"+res_down;
}
else{
res = ""+zhenshu;
}
}
else
{
int g = gcd(top,down);
if(g!=0)
{
top/=g;
down/=g;
}
res = top+"/"+down;
}
if(top==0)
{
res = "0";
}
}
// System.out.println(res);
return res;
}
/**
* 将Expression的表达式中的算子出现的小数类型转换为真分数
* @param e 计算表达式Expression类
*/
public static void convertToFraction(Expression e)
{
int len = e.getLen();
elements = new String[len];
for(int i=0;i<len;i++)
{
if(e.getNums()[i] instanceof Double) {
elements[i] = toFraction((double) e.getNums()[i]);
}
else
{
elements[i] = e.getNums()[i].toString();
}
}
}
/**
* 比较分数大小
* @param pre 左算子
* @param next 右算子
* @return String
*
*/
public static String fractionCompare(String pre, String next)
{
//ans为比较大小符号
String ans = null;
if(!pre.contains("/")&&!next.contains("/"))
{
Integer pre_ = Integer.parseInt(pre);
Integer next_ = Integer.parseInt(next);
if(pre_.equals(next_))
{
ans = "=";
}
else if(pre_>next_)
{
ans = ">";
}
else if(pre_<next_)
{
ans = "<";
}
}
else if(pre.contains("/")&&next.contains("/"))
{
Integer res_top = null;
Integer res_down = null;
Integer pre_top = Integer.parseInt(pre.substring(0, pre.indexOf("/")));
Integer pre_down = Integer.parseInt(pre.substring(pre.indexOf("/") + 1));
Integer next_top = Integer.parseInt(next.substring(0, next.indexOf("/")));
Integer next_down = Integer.parseInt(next.substring(next.indexOf("/") + 1));
res_down = lcm(pre_down, next_down);
next_top *= (res_down / next_down);
pre_top *= (res_down / pre_down);
if(pre_top>next_top)
{
ans = ">";
}
else if(pre_top<next_top)
{
ans = "<";
}
else if(pre_top.equals(next_top))
{
ans = "=";
}
}
else if(!pre.contains("/")&&next.contains("/"))
{
Integer next_top = Integer.parseInt(next.substring(0, next.indexOf("/")));
Integer next_down = Integer.parseInt(next.substring(next.indexOf("/") + 1));
Integer pre_top = Integer.parseInt(pre)*next_down;
if(pre_top>next_top)
{
ans = ">";
}
else if(pre_top<next_top)
{
ans = "<";
}
else
{
ans = "=";
}
}
else if(pre.contains("/")&&!next.contains("/"))
{
Integer pre_top = Integer.parseInt(pre.substring(0, pre.indexOf("/")));
Integer pre_down = Integer.parseInt(pre.substring(pre.indexOf("/") + 1));
Integer next_top = Integer.parseInt(next)*pre_down;
if(pre_top>next_top)
{
ans = ">";
}
else if(pre_top<next_top)
{
ans = "<";
}
else
{
ans = "=";
}
}
return ans;
}
/**
* 模拟分数加法
* @param pre 左算子
* @param next 右算子
* @return String
*/
public static String fractionAdd(String pre, String next)
{
Integer res_top = null;
Integer res_down = null;
if(pre.contains("/")&&next.contains("/")) {
Integer pre_top = Integer.parseInt(pre.substring(0, pre.indexOf("/")));
Integer pre_down = Integer.parseInt(pre.substring(pre.indexOf("/") + 1));
Integer next_top = Integer.parseInt(next.substring(0, next.indexOf("/")));
Integer next_down = Integer.parseInt(next.substring(next.indexOf("/") + 1));
res_down = lcm(pre_down, next_down);
pre_top *= (res_down / pre_down);
next_top *= (res_down / next_down);
res_top = pre_top + next_top;
}
else if(!pre.contains("/")&&!next.contains("/"))
{
String res = String.valueOf(Integer.parseInt(pre)+Integer.parseInt(next));
return res;
}
else if(pre.contains("/")&&!next.contains("/"))
{
Integer pre_top = Integer.parseInt(pre.substring(0,pre.indexOf("/")));
Integer pre_down = Integer.parseInt(pre.substring(pre.indexOf("/")+1));
res_down = pre_down;
res_top = pre_top+Integer.parseInt(next)*pre_down;
getGCD(res_top,res_down);
}
else if(!pre.contains("/")&&next.contains("/"))
{
Integer next_top = Integer.parseInt(next.substring(0,next.indexOf("/")));
Integer next_down = Integer.parseInt(next.substring(next.indexOf("/")+1));
res_down = next_down;
res_top = next_top+Integer.parseInt(pre)*next_down;
getGCD(res_top,res_down);
}
if(res_top==null||res_down==null)
{
return "0";
}
else {
return res_top+"/"+res_down;}
}
/**
* 模拟分数减法
* @param pre 左算子
* @param next 右算子
* @return String
*/
public static String fractionSubstract(String pre, String next)
{
Integer res_top = 0;
Integer res_down = 0;
if(pre.contains("/")&&next.contains("/")) {
Integer pre_top = Integer.parseInt(pre.substring(0, pre.indexOf("/")));
Integer pre_down = Integer.parseInt(pre.substring(pre.indexOf("/") + 1));
Integer next_top = Integer.parseInt(next.substring(0, next.indexOf("/")));
Integer next_down = Integer.parseInt(next.substring(next.indexOf("/") + 1));
res_down = lcm(pre_down, next_down);
pre_top *= (res_down / pre_down);
next_top *= (res_down / next_down);
res_top = pre_top - next_top;
getGCD(res_top,res_down);
}
else if(!pre.contains("/")&&!next.contains("/"))
{
Integer res = Integer.parseInt(pre)-Integer.parseInt(next);
return ""+res;
}
else if(pre.contains("/")&&!next.contains("/"))
{
Integer pre_top = Integer.parseInt(pre.substring(0,pre.indexOf("/")));
Integer pre_down = Integer.parseInt(pre.substring(pre.indexOf("/")+1));
res_down = pre_down;
res_top = pre_top-Integer.parseInt(next)*pre_down;
getGCD(res_top,res_down);
}
else if(!pre.contains("/")&&next.contains("/"))
{
Integer next_top = Integer.parseInt(next.substring(0,next.indexOf("/")));
Integer next_down = Integer.parseInt(next.substring(next.indexOf("/")+1));
res_down = next_down;
res_top = next_top-Integer.parseInt(pre)*next_down;
getGCD(res_top,res_down);
}
if(res_top==null||res_down==null)
{
return "0";
}
else {
return res_top+"/"+res_down;}
}
/**
* 模拟分数乘法
* @param pre 左算子
* @param next 右算子
* @return String
*/
public static String fractionMultiply(String pre, String next)
{
Integer res_top = 0;
Integer res_down = 0;
if(pre.contains("/")&&next.contains("/"))
{
Integer pre_top = Integer.parseInt(pre.substring(0,pre.indexOf("/")));
Integer pre_down = Integer.parseInt(pre.substring(pre.indexOf("/")+1));
Integer next_top = Integer.parseInt(next.substring(0,next.indexOf("/")));
Integer next_down = Integer.parseInt(next.substring(next.indexOf("/")+1));
res_top = pre_top*next_top;
res_down = pre_down*next_down;
getGCD(res_top,res_down);
}
else if(!pre.contains("/")&&!next.contains("/")){
Integer res = Integer.parseInt(pre)*Integer.parseInt(next);
return ""+res;
}
else if(!pre.contains("/")&&next.contains("/"))
{
Integer next_top = Integer.parseInt(next.substring(0,next.indexOf("/")));
Integer next_down = Integer.parseInt(next.substring(next.indexOf("/")+1));
res_top = next_top*Integer.parseInt(pre);
res_down = next_down;
getGCD(res_top,res_down);
}
else if(pre.contains("/")&&!next.contains("/"))
{
Integer pre_top = Integer.parseInt(pre.substring(0,pre.indexOf("/")));
Integer pre_down = Integer.parseInt(pre.substring(pre.indexOf("/")+1));
res_top = pre_top*Integer.parseInt(next);
res_down = pre_down;
getGCD(res_top,res_down);
}
if(res_top==null||res_down==null)
{
return "0";
}
else {
return res_top+"/"+res_down;}
}
/**
* moni分数除法
* @param pre 左算子
* @param next 右算子
* @return String
*/
public static String fractionDivide(String pre, String next)
{
Integer res_top = null;
Integer res_down = null;
if(pre.contains("/")&&next.contains("/"))
{
Integer next_top = Integer.parseInt(next.substring(next.indexOf("/")+1));
Integer next_down = Integer.parseInt(next.substring(0,next.indexOf("/")));
next = next_top+"/"+next_down;
return fractionMultiply(pre,next);
}
else if(!pre.contains("/")&&!next.contains("/"))
{
res_top = Integer.parseInt(pre);
res_down = Integer.parseInt(next);
getGCD(res_top,res_down);
}
else if(!pre.contains("/")&&next.contains("/"))
{
Integer next_top = Integer.parseInt(next.substring(next.indexOf("/")+1));
Integer next_down = Integer.parseInt(next.substring(0,next.indexOf("/")));
next = next_top+"/"+next_down;
return fractionMultiply(pre,next);
}
else if(pre.contains("/")&&!next.contains("/"))
{
Integer pre_top = Integer.parseInt(pre.substring(0,pre.indexOf("/")));
Integer pre_down = Integer.parseInt(pre.substring(pre.indexOf("/")+1));
res_down = pre_down*Integer.parseInt(next);
res_top = pre_top;
getGCD(res_top,res_down);
}
if(res_top==0||res_down==null)
{
return null;
}
else {
return res_top+"/"+res_down;}
}
}
View层
com/laomi/view/Main.java
:
public class Main {
private static final String AMOUNT = "-n";
private static final String NUMBER_BOUND = "-r";
private static final String EXERCISE_FILE = "-e";
private static final String ANSWER_FILE = "-a";
public static void main(String[] args) {
if (checkArgs(args, "-n", "-r")) {
generate(args);
} else if (checkArgs(args, "-e", "-a")) {
correct(args);
} else {
System.out.println("参数不合法");
}
}
private static void generate(String[] args) {
int amount = -1;
int numberBound = -1;
try {
amount = Integer.parseInt(args[findArgIndex(args, AMOUNT)]);
numberBound = Integer.parseInt(args[findArgIndex(args, NUMBER_BOUND)]);
} catch (Exception ignore) {
System.out.println("参数不合法");
System.exit(-1);
}
if (amount == -1 || numberBound == -1) {
System.out.println("参数不合法");
System.exit(-1);
}
Producer producer = new Producer();
producer.setAmount(amount);
producer.setNumberBound(numberBound);
// 生成表达式
Set<Expression> expressions = producer.produce();
// 分别生成算术表达式和答案文件
ExerciseBook.generateFile(new ArrayList<>(expressions));
System.out.println("成功生成 " + amount + " 道算术题");
}
private static void correct(String[] args) {
String exerciseFile = null;
String answerFile = null;
try {
exerciseFile = args[findArgIndex(args, EXERCISE_FILE)];
answerFile = args[findArgIndex(args, ANSWER_FILE)];
} catch (Exception ignore) {
System.out.println("参数不合法");
System.exit(-1);
}
ExerciseBook.checkAnswers(exerciseFile, answerFile);
System.out.println("分数已批改完毕 d=====( ̄▽ ̄*)b");
}
private static boolean checkArgs(String[] args, String...target) {
if (target == null || args == null) {
return false;
}
for (String t : target) {
boolean exist = false;
for (String arg : args) {
if (t.equals(arg)) {
exist = true;
break;
}
}
if (!exist) {
return false;
}
}
return true;
}
private static int findArgIndex(String[] args, String target) {
if (target == null || args == null) {
return -1;
}
for (int i = 0; i < args.length; i++) {
if (target.equals(args[i])) {
return i + 1;
}
}
return -1;
}
}
6. 测试说明
以下所有的测试文件都位于 项目Github地址 的 sample 文件夹内
生成题目
批改作业
7. 实际耗时
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 90 | 50 |
· Estimate | · 估计这个任务需要多少时间 | 90 | 50 |
Development | 开发 | 1260 | 1330 |
· Analysis | · 需求分析 (包括学习新技术) | 180 | 120 |
· Design Spec | · 生成设计文档 | 30 | 10 |
· Design Review | · 设计复审 (和同事审核设计文档) | 120 | 150 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 30 | 10 |
· Design | · 具体设计 | 240 | 360 |
· Coding | · 具体编码 | 300 | 360 |
· Code Review | · 代码复审 | 240 | 300 |
· Test | · 测试(自我测试,修改代码,提交修改) | 120 | 20 |
Reporting | 报告 | 150 | 280 |
· Test Report | · 测试报告 | 60 | 80 |
· Size Measurement | · 计算工作量 | 30 | 20 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 60 | 180 |
合计 | 1500 | 1660 |
8. 项目小结
这个项目从发布题目到最后真是开始写代码,我们花了很多的时在讨论具体应该如何实现上。相较于上个个人项目的单打独斗一个人思考需求和实现,结对编程很明显我们能在讨论过程发现彼此想的不周到的地方。用了几天时间做完这个项目,我们发现,程序中的错误减少了,质量提高了不少,这给我们后来测试代码提供了更多宝贵的时间和精力。除此之外,在两个人的结对过程中,由于有相互监督,因此无论是谁充当键盘手,他的想法都要受到对方的评价。正式因为有这种压力在,所以相比于上个单词统计项目,这个项目的代码质量也比之前更上了一个阶梯。在实际结对编程中,我们划分了模块,算式表达式的生成和答案计算主要由陈同学担任键盘手,而张同学则作为监督者提供指导意见;而到了去重以及分母转换模块,则换成张同学做键盘手,陈同学在负责围观和监督,两种身份不同转换,也算是一种劳逸结合。
陈同学对张同学对结对表现的评价:这个项目最终能很快写完离不开张同学的指导,跟她一起结对编程真的非常愉快( ̄︶ ̄*))。比如说我们在讨论如何生成括号时,我先提出了一种使用模拟的暴力方法进行括号的匹配,我那时候执意认为这样子可以求解,折腾了一个晚上后还是投降了。最终还是张同学给我提供了可以采用二叉树的思想递归生成括号的思路,我才能找到合理的解决方法,这样的场合还有很多很多。可以说如果没有结对编程,没有张同学在旁边认真听我叽叽呱呱的话,很多地方我真的无从下笔,她是一名很懂得倾听的coder,你会注意到她在很认真地听你讲话。张同学真的是一名非常认真负责的同学,在转换真分数的问题上我们一开始没有考虑周全,导致后期改代码非常麻烦,这里面也有我的问题,是我没有及时将具体的编码实现跟张同学讲,害得她要改好多东西....她真的特别辛苦。当然,在这个过程中能看到张同学的进步是我最高兴的,将来还有很多很多合作,一起加油吧~
张同学对陈同学的结对表现的评价:首先商业互吹下陈俊旭同学,跟陈同学结对学到很多东西,刚开始对这个项目只是对某些需求的实现有想法,但是由于统筹能力缺乏对整个项目的整体设计比较懵,所以陈同学在设计这方面较我而言做了很多工作。在和陈同学结对的过程中学到了很多,包括对封装性的进一步了解和对整体设计有了一些感悟。结对过程一开始由陈同学当键盘手,而我是在旁边负责及时差错和提供思路。<( ̄︶ ̄)↗[GO!]但是到了后面的分数计算和表达式查重就由我接锅,结果被自己坑了一把,没有看清陈同学在之前写的表达式的具体计算方式是由小数计算,所以在得到答案是严重失去了精度,只好模拟分数的四则运算过程。实际上我是对整个项目的结构的修改有一定的不好的影响,而且代码规范还是没有达到陈同学的预期.....((/- -)/。但是和陈同学的合作过程很愉快,我有什么问题他都会详细为我解答,作为一个messy coder估计我的结对伙伴很头疼但是感谢他坚持了下来~~