PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 1200 | 1680 |
· Estimate | · 估计这个任务需要多少时间 | 1200 | 1680 |
Development | 开发 | 1000 | 1540 |
· Analysis | · 需求分析 (包括学习新技术) | 120 | 240 |
· Design Spec | · 生成设计文档 | 30 | 40 |
· Design Review | · 设计复审 (和同事审核设计文档) | 20 | 30 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 30 | 10 |
· Design | · 具体设计 | 30 | 70 |
· Coding | · 具体编码 | 600 | 1000 |
· Code Review | · 代码复审 | 100 | 120 |
· Test | · 测试(自我测试,修改代码,提交修改) | 70 | 30 |
Reporting | 报告 | 80 | 90 |
· Test Report | · 测试报告 | 30 | 20 |
· Size Measurement | · 计算工作量 | 20 | 30 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 30 | 40 |
合计 | 1400 | 1680 |
题目数字以及运算符要求:
e = n | e1 + e2 | e1 ? e2 | e1 × e2 | e1 ÷ e2 | (e),
其中e, e1和e2为表达式,n为自然数或真分数。
生成题目具体操作过程及格式:
使用 -n 参数控制生成题目的个数,例如: Myapp.exe -n 10 将生成10个题目。
使用 -r 参数控制题目中数值(自然数、真分数和真分数分母)的范围,例如 :Myapp.exe -r 10 将生成10以内(不包括10)的四则运算题目。该参数可以设置为1或其他自然数。该参数必须给定,否 则程序报错并给出帮助信息。
生成的题目中计算过程不能产生负数,也就是说算术表达式中如果存在形如e1? e2的子表达式,那么e1≥ e2。
生成的题目中如果存在形如e1÷ e2的子表达式,那么其结果应是真分数。
每道题目中出现的运算符个数不超过3个。
程序一次运行生成的题目不能重复,即任何两道题目不能通过有限次交换+和×左右的算术表达式变换为同一道题目。例如,23 + 45 = 和45 + 23 = 是重复的题目,6 × 8 = 和8 × 6 = 也是重复的题目。3+(2+1)和1+2+3这两个题目是重复的,由于+是左结合的,1+2+3等价于(1+2)+3,也就是3+(1+2),也就是3+(2+1)。但是1+2+3和3+2+1是不重复的两道题,因为1+2+3等价于(1+2)+3,而3+2+1等价于(3+2)+1,它们之间不能通过有限次交换变成同一个题目。
生成的题目存入执行程序的当前目录下的Exercises.txt文件,格式如下:
其中真分数在输入输出时采用如下格式,真分数五分之三表示为3/5,真分数二又八分之三表示为2’3/8。
在生成题目的同时,计算出所有题目的答案,并存入执行程序的当前目录下的Answers.txt文件,格式如下:
1.答案1
2.答案2
...
真分数的运算如下例所示:1/6 + 1/8 = 7/24。
程序应能支持一万道题目的生成。
程序支持对给定的题目文件和答案文件,判定答案中的对错并进行数量统计,输入参数如下:
Myapp.exe -e
Correct: 5 (1, 3, 5, 7, 9)
Wrong: 5 (2, 4, 6, 8, 10)
其中“:”后面的数字5表示对/错的题目的数量,括号内的是对/错题目的编号。为简单起见,假设输入的题目都是按照顺序编号的符合规范的题目。
数字生成:设计分为整数和分数两种类型,在生成分数的过程中检索是否有分母为0或者分子为零的情况,进行迭代生成。
符号生成:我的设计是操作符和括号是分开的。操作符是1/4的概率生成,而括号我选择随机选取表达式的特定位置做插入。
划分:我采取的措施是用数字和符号用空格划分,恰好也满足了项目的需求
题目计算和去重:题目去重和计算是同步的,通过把题目和计算出来的答案和存储好的题目和答案进行计算,检索答案是否一致再去判断符号类型、数字格式来判断题目是否重复,双重保障。计算是通过把生成好的表达式转换成后缀表达式来得到结果,这个简单,不摊开来讲。去重伪代码如下:
if (容器存在一毛一样的表达式){
重新生成表达式
}
if (容器里存在一毛一样的结果){
if (判断数字类型、符号是否完全符合,判断顺序是否有变换){
重新生成表达式
}
}
运算过程产生负数的处理:在计算过程中,碰到产生负数的结果,直接表达式计算返回,然后重新生成新的表达式。
对分数及整数的计算
/***
* 相加操作
*/
ADD("+") {
@Override
public String calculate(String a, String b) {
boolean flagA = a.contains("/");
boolean flagB = b.contains("/");
//两个都是分数
if (flagA && flagB) {
int[] anInt = ResolveUtil.analysis(a);
int[] bnInt = ResolveUtil.analysis(b);
//以AB为分母
int denominator = anInt[1] * bnInt[1];
//相加后的分子
int molecule = anInt[0] * bnInt[1] + anInt[1] * bnInt[0];
return ResolveUtil.createFraction(molecule, denominator);
} else if (flagA) {
int[] anInt = ResolveUtil.analysis(a);
//直接更新分子便可
anInt[0] += Integer.parseInt(b) * anInt[1];
return ResolveUtil.createFraction(anInt[0], anInt[1]);
} else if (flagB) {
int[] bnInt = ResolveUtil.analysis(b);
//直接更新分子便可
bnInt[0] += Integer.parseInt(a) * bnInt[1];
return ResolveUtil.createFraction(bnInt[0], bnInt[1]);
} else {
return String.valueOf(Integer.parseInt(a) + Integer.parseInt(b));
}
}
},
/***
* 相乘操作
*/
MULTIPLY("×") {
@Override
public String calculate(String a, String b) {
boolean flagA = a.contains("/");
boolean flagB = b.contains("/");
if (flagA && flagB) {
int[] anInt = ResolveUtil.analysis(a);
int[] bnInt = ResolveUtil.analysis(b);
//以AB为分母
int denominator = anInt[1] * bnInt[1];
//分子相乘
int molecule = anInt[0] * bnInt[0];
return ResolveUtil.createFraction(molecule, denominator);
} else if (flagA) {
int[] anInt = ResolveUtil.analysis(a);
return ResolveUtil.createFraction(anInt[0] * Integer.parseInt(b), anInt[1]);
} else if (flagB) {
int[] bnInt = ResolveUtil.analysis(b);
return ResolveUtil.createFraction(bnInt[0] * Integer.parseInt(a), bnInt[1]);
} else {
return String.valueOf(Integer.parseInt(a) * Integer.parseInt(b));
}
}
},
/***
* 相除操作
*/
DIVIDE("÷") {
@Override
public String calculate(String a, String b) {
//除法,从另外一种角度来说,是乘法的倒转,所以,只需要把b分子分母倒过来用乘法就行了
boolean flag = b.contains("/");
//新的数b的字符串
String newB;
//判断b是否为分数
if (flag) {
int[] bnInt = ResolveUtil.analysis(b);
newB = ResolveUtil.createFraction(bnInt[1], bnInt[0]);
} else {
newB = 1 + "/" + b;
}
return Symbol.MULTIPLY.calculate(a, newB);
}
},
/***
* 相减操作
*/
SUB("-") {
@Override
public String calculate(String a, String b) {
//减是加的特例,把b弄成-就可以了
return Symbol.ADD.calculate(a, "-" + b);
}
},
中缀转后缀表达式
/***
* 中缀表达式转换成后缀表达式
* @param expression 表达式
* @return 数组
*/
private String[] middleToAfter(String expression) {
//用来转换的栈
Stack<String> stack = new Stack<>();
//表达式每个字符前后都会生成一个空格
String[] strings = expression.split("\\s");
//返回的list
List<String> stringList = new ArrayList<>(strings.length);
for (int index = 0; index < strings.length; index++) {
if (‘0‘ <= strings[index].charAt(0) && strings[index].charAt(0) <= ‘9‘) {
//数字直接输出
stringList.add(strings[index]);
} else if (strings[index].equals(Symbol.BEGIN.getSymbol())) {
//开始括号压进栈
stack.push(strings[index]);
} else if (strings[index].equals(Symbol.END.getSymbol())) {
//把所有运算符都出栈
while (!stack.peek().equals(Symbol.BEGIN.getSymbol())) {
stringList.add(stack.pop());
}
//出栈开始括号
stack.pop();
} else if (strings[index].equals(Symbol.MULTIPLY.getSymbol())
|| strings[index].equals(Symbol.DIVIDE.getSymbol())) {
//判断上一级符号是什么
boolean flag = !stack.isEmpty() && (stack.peek().equals(Symbol.MULTIPLY.getSymbol())
|| stack.peek().equals(Symbol.DIVIDE.getSymbol()));
if (flag) {
stringList.add(stack.pop());
}
stack.push(strings[index]);
} else if (strings[index].equals(Symbol.SUB.getSymbol())
|| strings[index].equals(Symbol.ADD.getSymbol())) {
//此处应该为+,-号
boolean flag = !stack.isEmpty() && (stack.peek().equals(Symbol.ADD.getSymbol())
|| stack.peek().equals(Symbol.SUB.getSymbol()));
if (flag) {
stringList.add(stack.pop());
}
stack.push(strings[index]);
} else {
//有其他符号,直接跳出,可能是=号
break;
}
}
while (!stack.isEmpty()) {
stringList.add(stack.pop());
}
//返回数组
return stringList.toArray(new String[0]);
}
后缀计算
/***
* 计算表达式
* @param expression 表达式
* @param permit 允许存在负数的运算过程
* @return 结果
*/
public String calculate(String expression, boolean permit) {
if (expression == null) {
return null;
}
//先生成后缀表达式数组,然后手动控制数组进行操作
String[] afterExp = middleToAfter(expression);
Stack<String> stack = new Stack<>();
try {
for (int index = 0; index < afterExp.length; index++) {
if (afterExp[index].matches("[0-9/‘]+")) {
stack.push(afterExp[index]);
} else {
String b = stack.pop();
String a = stack.pop();
String result = Symbol.value(afterExp[index]).calculate(a, b);
//计算过程中存在负数,重新生成表达式
if (result.startsWith("-") && !permit) {
return null;
}
stack.push(result);
}
}
} catch (NullPointerException e) {
e.printStackTrace();
} catch (Exception e) {
System.out.println("存在表达式不合法");
}
return stack.pop();
}
生成表达式
/***
* 生成表达式
* @return 生成表达式
*/
private String generateExpression() {
//随机运算符大小
int operatorSize = (int) (Math.random() * MAX_OPERATOR_SIZE) + 1;
int numberSize = operatorSize + 1;
//判断是否需要生成括号,1/4的概率
boolean flag = (int) (Math.random() * MAX_OPERATOR_SIZE) == 0;
//标记(产生的位置)
int mark = -1;
if (flag) {
//随机插入括号的位置
mark = (int) (Math.random() * operatorSize);
}
StringBuilder expression = new StringBuilder();
//遍历产生数字和符号,你一下我一下
for (int i = 0; i < numberSize; i++) {
if (mark == i) {
myAppend(expression, "(");
}
//生成数字
myAppend(expression, (int) (Math.random() * 2) == 0 ? generateFraction() : generateInt());
//判断是否加入结束符号,判断是否结尾
if (mark >= 0 && mark < i) {
//已经到了表达式结尾, 此时必须结束
if (i == operatorSize) {
myAppend(expression, ")");
break;
}
//判断是否需要结束
flag = (int) (Math.random() * 2) == 0;
if (flag) {
myAppend(expression, ")");
mark = -1;
}
}
if (i < operatorSize) {
//然后生成一个操作符
myAppend(expression, generateOperator());
}
}
//最后补充等号
expression.append("=");
return expression.toString();
}
去重校验
/***
* 检查表达式是否已经存在或者重复
* @param expression 表达式
* @param result 结果
* @return 是否重复
*/
private boolean checkExpressionExistAndResultIllegal(String expression, String result) {
if (Objects.isNull(result)) {
return true;
}
//当前没有表达式
if (nowExpressionSize == 0) {
return false;
}
//API的一些操作也是循环,效率低下,手动循环
for (int i = 0, j = nowExpressionSize - 1; i <= j; i++, j--) {
if (expressionList.get(i).equals(expression) || expressionList.get(j).equals(expression)) {
return true;
}
//查看是否答案有相同的
if (answerList.get(i).equals(result)) {
return checkCharEquals(expressionList.get(i), expression);
} else if (answerList.get(j).equals(result)) {
return checkCharEquals(expressionList.get(j), expression);
}
}
return false;
}
/***
*
* @param oldExpression 存在的表达式
* @param newExpression 还没存进去的表达式
* @return 相同,不相同
*/
private boolean checkCharEquals(String oldExpression, String newExpression) {
String[] oldExpressionArrays = oldExpression.split("\\s+");
String[] newExpressionArrays = newExpression.split("\\s+");
int oldExpressionNumber = 0;
int equalsNumber = 0;
//是否为数字
boolean flag;
//开始遍历
for (String oldString : oldExpressionArrays) {
flag = oldExpression.matches("[0-9‘/]+");
if (flag) {
oldExpressionNumber++;
}
//比对
for (String newString : newExpressionArrays) {
if (oldString.equals(newString)) {
equalsNumber++;
}
}
}
答案校对
//保存正确/错误题号的队列
private List<Integer> correctList = new ArrayList<>();
private List<Integer> wrongList = new ArrayList<>();
/**
* 校验待检测文件状态
* @param exersicesFile
* @param myAnswer
* @Author Naren
*/
public void checkFile(String exersicesFile,String myAnswer) {
//存储表达式的文件
File expFile = new File(exersicesFile);//Exercise002.txt
//待校验答案的文件
File myAnsFile = new File(myAnswer);//myAnswers001.txt
//待校验答案文件不存在
if(!myAnsFile.exists()){
//System.out.println("未找到待检验答案文件。");
new Tips().displayTips("noExpTip.png");
return;
}
//如果表达式文件不存在
if(!expFile.exists()) {
//System.out.println("未找到指定题目文件。");
new Tips().displayTips("noAnsTip.png");
return;
}
//如果全部文件名都正确,检测待校对题目文件是否存在于系统生成历史中
String id = exersicesFile.substring(9,12);
String sysAnsFile = "Answers" + id + ".txt"; //Myapp.exe -e Exercises001.txt -a myAnswers001.txt
File ansFile = new File(sysAnsFile);//Answers002.txt(不存在)
//若系统中不存在与题目文件相符合的答案文件
if(!ansFile.exists()){
try {
FileReader fr = new FileReader(expFile);
BufferedReader br = new BufferedReader(fr);
String content = null;
ArrayList<String> questionList = new ArrayList<>();
while((content = br.readLine()) != null){//(?m)^.*$
content = content.split("\\.")[1];
questionList.add(content);
}
//调用起廷方法获得答案队列
Expression ex = new Expression(new Calculate());
List<String> answerList = ex.getCorrectAnswerList(questionList);
//比对
checkAnswer(myAnsFile,answerList);
} catch (Exception e) {
System.out.println("Class:AnswerChecking,Method:checkFile(String,String) is wrong!");
}
}else{
//调用本类检验方法比对答案文件
checkAnswer(myAnsFile,ansFile);
}
}
/**
* 将待校验答案文件与现场计算出的答案队列进行比对
* @param myAnswer
* @param answerList
* @author Naren
*/
private void checkAnswer(File myAnswer, List<String> answerList){
try {
//待检测答案文件
FileReader fr1 = new FileReader(myAnswer);
BufferedReader br1 = new BufferedReader(fr1);
LinkedHashMap<Integer,String> map1 = new LinkedHashMap<>();
String content = "";
while((content = br1.readLine()) != null){
content = content.replaceAll(" +", "").replaceAll("\uFEFF", "");
//map1待校验答案:key:序号,value:答案
map1.put(Integer.valueOf(content.split("\\.")[0]),content.split("\\.")[1]);
}
//开始比对
for (int i = 0; i < answerList.size(); i++) {
if(map1.containsKey(i + 1)){
if(map1.get(i + 1).equals(answerList.get(i))) {
correctList.add(i + 1);//正确题号添加进队列
}
else{
wrongList.add(i + 1);//错误题号添加进队列
}
}else{
//漏写
wrongList.add(i + 1);
}
}
//将校验结果写入文件
//System.out.println("检验信息已写入Grade.txt文件。");
new Tips().displayTips("checkSuccess.png");
new DataStorage().storeCheckInfo(correctList,wrongList);
} catch (Exception e) {
System.out.println("Class:AnswerChecking,Method:checkAnswer(File,List) is wrong!");
}
}
/**
* 【重载】将待校验答案文件与本地答案文件进行比对
* @param myAnswer 待校验答案文件
* @param ansFile 正确答案文件
* @author Naren
*/
private void checkAnswer(File myAnswer, File ansFile) {
try {
FileReader fr1 = new FileReader(myAnswer);
FileReader fr2 = new FileReader(ansFile);
BufferedReader br1 = new BufferedReader(fr1);
BufferedReader br2 = new BufferedReader(fr2);
LinkedHashMap<Integer,String> map1 = new LinkedHashMap<>();//待检测 key:序号,value:答案
LinkedHashMap<Integer,String> map2 = new LinkedHashMap<>();//正 确 key:序号,value:答案
//分别按行读出答案
String content = "";
while((content = br1.readLine()) != null){
content = content.replaceAll(" +", "").replaceAll("\uFEFF", "");
map1.put(Integer.valueOf(content.split("\\.")[0]),content.split("\\.")[1]);
}
while((content = br2.readLine()) != null){
content = content.replaceAll(" +", "").replaceAll("\uFEFF", "");
map2.put(Integer.valueOf(content.split("\\.")[0]),content.split("\\.")[1]);
}
//开始比对
for (int i = 0; i < map2.size(); i++) {
if(map1.containsKey(i + 1)){
if(map1.get(i + 1).equals(map2.get(i + 1))) {
correctList.add(i + 1);//正确题号添加进队列
}
else{
wrongList.add(i + 1);//错误题号添加进队列
}
}else{
//漏写
wrongList.add(i + 1);
}
}
//将校验结果写入文件
//System.out.println("检验信息已写入Grade.txt文件。");
new Tips().displayTips("checkSuccess.png");
new DataStorage().storeCheckInfo(correctList,wrongList);
} catch (Exception e) {
System.out.println("Class:AnswerChecking,Method:checkAnswer(File,File) is wrong!");
}
}
7.1.1错误示例
生成错误
校对错误
7.1.2正确示例
正确生成
正确生成100道题目
正确生成1万2千道题目
正确校验
7.2.1错误示例
7.2.2正确示例
因为编码过程中即有不断测试,且两个人互相挑bug,最后测试时手动覆盖了可能的输入,可以说“程序结果是正确的”的期望接近1。
原文:https://www.cnblogs.com/narenblogs/p/12694647.html