结对项目
结对编程(JAVA实现)
这个作业属于哪个课程 | https://edu.cnblogs.com/campus/gdgy/Networkengineering1834 |
---|---|
这个作业要求在哪里 | https://edu.cnblogs.com/campus/gdgy/Networkengineering1834/homework/11148 |
这个作业的目标 | 实现一个自动生成小学四则运算题目的命令行程序 |
项目成员
郭永轩(3118005272) 龚广健(3118005271)
GitHub地址
https://github.com/wobushidamowang/ForCalculate
PSP
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 80 | 120 |
· Estimate | · 估计这个任务需要多少时间 | 80 | 120 |
Development | 开发 | 1440 | 1530 |
· Analysis | · 需求分析 (包括学习新技术) | 40 | 50 |
· Design Spec | · 生成设计文档 | 20 | 15 |
· Design Review | · 设计复审 (和同事审核设计文档) | 20 | 15 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 20 | 20 |
· Design | · 具体设计 | 60 | 60 |
· Coding | · 具体编码 | 900 | 960 |
· Code Review | · 代码复审 | 30 | 30 |
· Test | · 测试(自我测试,修改代码,提交修改) | 350 | 380 |
Reporting | 报告 | 100 | 110 |
· Test Report | · 测试报告 | 40 | 60 |
· Size Measurement | · 计算工作量 | 20 | 20 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 40 | 30 |
合计 | 1620 | 1760 |
性能分析
各数据类型占用内存
各模块消耗的时间
以上是进行测试的性能测试图,ArrayList类型内存占用最高,查重模块时间消耗最多
2s内完成10个测试,运行速度还是挺快的
设计实现过程
1.设计流程图
生成题目流程图
算术表达式中序变后序流程图
2.项目目录
实体类:分数类Fraction,算术表达式类OperationExpression
工具类:查重类DuplicateCheck
服务类:计算类CalculateService,答案判错类CheckService,表达式生成类getExpression
项目入口:OperationPro
3.关键设计思路
考虑到分数的实现,因为java不存在可以处理分数运算的类,所以应将整数与分数作为一个对象来处理,即整数就是分母为1的分数。也因此做了实体类Fraction
受到Fraction启发,做了实体类OperationExpression,它的属性包含了表达式及其答案
题目生成设计思路:
于是就可以实例化一个OperationExpression对象e,运用getExpression类的getExp方法生成一个表达式设置为e的表达式,getExp()方法中调用了CalculateService的calculate()计算方法得到答案设置为e的答案。calculate()方法中产生了负数则会返回空值,可根据这个空值判断表达式是否存在负数,若存在则丢弃重新生成。其后,还会经过DuplicateCheck类的查重。通过上述步骤,即可生成一道题目。
关键代码说明
表达式生成类getExpression
import bean.Fraction;
import bean.OperationExpression;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import static java.lang.System.out;
public class getExpression {
private static Random r = new Random();
static List l = new ArrayList();
//获得字符串集合,并生成表达式
public List getExp(OperationExpression e,int limit){
Fraction f1 = new Fraction(r.nextBoolean(),limit);
List stringList = new ArrayList();
List l = new ArrayList();
l.add(f1);
stringList.add(f1.toString());
int a = r.nextInt(3); // [0,3),取值为0/1/2,随机生成运算符个数
do{
express(e,l,stringList,limit);
a--;
}while (a>=0);
Fraction f =CalculateService.calculate(l);
if(f==null){
return null;
}
e.setRes(f);
e.setStringList(stringList);
return stringList;
}
static void express(OperationExpression e,List l,List stringList,int limit){
Fraction f = new Fraction(r.nextBoolean(),limit);
//out.println(f.toString()+"f..");
int a = r.nextInt(4); //取值范围:[0,4)中的0/1/2/3,随机取运算符
switch (a){ //0:加,1:减,2:乘,3:除
case 0: l.add("+");l.add(f);
stringList.add("+");stringList.add(f.toString());
break;
case 1:
l.add("-");l.add(f);
stringList.add("-");stringList.add(f.toString());
break;
case 2: l.add("×");l.add(f);
stringList.add("×");stringList.add(f.toString());
break;
case 3: l.add("÷");l.add(f);
stringList.add("÷");stringList.add(f.toString());
break;
default:
out.println("出错");
}
e.setList(l);
}
}
计算类CalculateService
import bean.Fraction;
import java.util.List;
import java.util.Stack;
/**
* 提供计算服务
*/
public class CalculateService {
/**
* 中序变为后序
* @param list
* @return
*/
public static Stack toPostFix(List list){
Stack number = new Stack();
Stack<String> action = new Stack<>();
int symble = 0;
for (Object o : list) {
symble = flag(o, number, action);
switch (symble) {
case 1://数字直接入栈
number.push(o);
break;
case 2://操作符栈为空直接入栈
action.push((String) o);
break;
case 3://当前操作符比栈顶操作符优先级高入栈
action.push((String) o);
break;
case 4:
//弹出所有比当前操作符优先级高的,直到遇到左括号或者为空
while (!action.empty() && action.peek()!="(" && !prior((String)o,action.peek())){
number.push(action.pop());//action弹栈并压入number
}
action.push((String) o);//操作符压栈
break;
case 5://左括号无条件入操作栈
action.push((String) o);
break;
case 6:
first: while (!action.isEmpty()) {//action弹栈并压入number栈直到遇到左括号
String temp = action.pop();
if (temp.equals("(")) {
break first;
} else {
number.push(temp);
}
}
break;
default:
break;
}
}
Stack temp = new Stack();
//将剩下的操作符压入number栈中
for (String s : action) {
number.push(s);
}
//反序
for (Object o : number) {
temp.push(o);
}
return temp;
}
/**
* 中序转后序表达式的各种逻辑判断,将判断的结果送入toPostfix()进行各种情况的具体逻辑处理
*
* @param o
* @param number number栈
* @param action action栈
* @return 返回各种情况的symbol
*/
public static int flag(Object o, Stack number, Stack<String> action) {
if (o instanceof Fraction)
return 1;// number
//是操作符
String s = (String)o;
if (s.matches("(\\+)|(\\-)|(\\×)|(\\÷)")) {
if (action.isEmpty()) {
return 2;// action为空
} else if (prior(s, action.peek())) {
return 3;// action不为空,操作符优先级高于栈顶操作符
} else {
return 4;// action不为空,操作符优先级不高于栈顶操作符
}
}
if (s.matches("\\("))
return 5;// 左括号
if (s.matches("\\)"))
return 6;// 右括号
//都不是
return 0;
}
/**
* 判断操作符和栈顶操作符的优先级
* @param s1 操作符
* @param s2 栈顶操作符
* @return 优先级
*/
public static Boolean prior(String s1, String s2) {
if (s2.matches("\\(")) //任何操作符的优先级高于左括号
return true;
if (s1.matches("(\\×)|(\\÷)") && s2.matches("(\\+)|(\\-)"))
return true;
return false;
}
public static Fraction calculate(List list){
Stack stack = toPostFix(list);//转为后序表达
Stack<Fraction> newStack = new Stack();//存数字
for (Object o : stack) {
if(o instanceof Fraction){
//是数字
newStack.push((Fraction) o);
}else {
//是操作符
if(newStack.size()<2){
//遇到操作符时栈内元素至少为2
//TODO 可以报错
break;
}
Fraction a = newStack.pop();
Fraction b = newStack.pop();
switch ((String) o) {
case "+":
newStack.push(b.add(a));
break;
case "-":
Fraction fraction = b.sub(a);
//TODO 可以改为抛出减法异常
if(fraction.getNominator()<=0||fraction.getDenominator()<=0){
return null;
}
newStack.push(fraction);//计算结果并压栈,注意顺序
break;
case "×":
newStack.push(b.muti(a));
break;
case "÷":
newStack.push(b.div(a));
break;
default:
break;
}
}
}
return newStack.pop();
}
}
查重类DuplicateCheck
package utils;
import java.util.Iterator;
import java.util.List;
public class DuplicateCheck {
//l为判断的表达式集合,allList是已经存在的所有表达式集合
public boolean DuCheck(List l, List allList){
Iterator it = allList.iterator();
while (it.hasNext()){
List L = (List) it.next();
if(CheckList(l,L)) return true;
}
return false;
}
/*
*判断两个String类型的List是否完全相同
*大小一样,所有元素互相含有,元素顺序可以不一致
*/
//l1是l的形参,l2是allList中某个元素的形参
boolean CheckList(List l1,List l2){
if (l1 == l2) {
return true;
}
if (l1 == null && l2 == null)
{
return true;
}
if (l1 == null || l2 == null)
{
return false;
}
if (l1.size() != l2.size())
{
return false;
}
if (l1.containsAll(l2) && l2.containsAll(l1))
{
return true;
}
return false;
}
}
答案判错类CheckService
import java.io.*;
import java.util.HashMap;
import java.util.Map;
public class CheckService {
/*
* 用来检查用户结果的正确性.
*/
public static Map<String,String> Check(String checked,String answer) throws IOException{
Map<String,String> m = new HashMap<String,String>();
String error = "";
String correct = "";
int errornum = 0;
int correctnum = 0;
String str1 = "";
String str2 = "";
// checked = "D:\\资料\\1.txt";
// answer = "D:\\资料\\2.txt";
File checkedfile=new File(checked);
FileInputStream input1=new FileInputStream(checkedfile);
BufferedReader reader1=new BufferedReader(new InputStreamReader(input1));
File answerfile=new File(answer);
FileInputStream input2=new FileInputStream(answerfile);
BufferedReader reader2=new BufferedReader(new InputStreamReader(input2));
while((str1=reader1.readLine())!=null&&(str2=reader2.readLine())!=null){
if(!str1.trim().equals(str2.trim())){
// System.out.println(str1);
String[] str = str1.split("\\.");
error = error + str[0]+ ",";
errornum ++ ;
}else {
// System.out.println(str1);
String[] str = str1.split("\\.");
correct = correct + str[0] + ",";
correctnum ++;
}
}
if(error.equals("")){
error = "Wrong: " + errornum + "";
}else {
error = "Wrong: " + errornum + "(" + error.substring(0,error.length()-1) + ")";
}
if(correct.equals("")){
correct = "Correct: " + correctnum + "";
}else {
correct = "Correct: " + correctnum + "("+correct.substring(0, correct.length()-1)+")";
}
// System.out.println(error);
// System.out.println(correct);
m.put("wrong", error);
m.put("correct", correct);
return m;
}
}
测试运行
项目小结
测试运行
import bean.Fraction;
import bean.OperationExpression;
import org.junit.Test;
import java.util.List;
public class test {
@Test
public void test1(){
OperationPro.mainGenerate(10,10);
}
@Test
public void test2(){
OperationPro.mainCheck("D:\\developer\\ForCalculate\\Answer1.txt","D:\\developer\\ForCalculate\\Answer.txt");
}
@Test
public void test3(){
getExpression getExpression = new getExpression();
List exp = getExpression.getExp(new OperationExpression(null, null, null), 10);
System.out.println(exp);
}
@Test
public void test4(){
getExpression getExpression = new getExpression();
List exp = getExpression.getExp(new OperationExpression(null, null, null), 3);
System.out.println(exp);
}
@Test
public void test5(){
Fraction a = new Fraction(true,10);
Fraction b = new Fraction(true,10);
System.out.println(a.sub(b));
}
@Test
public void test6(){
Fraction a = new Fraction(false,20);
Fraction b = new Fraction(true,10);
System.out.println(a.div(b));
}
@Test
public void test7(){
Fraction a = new Fraction(false,10);
Fraction b = new Fraction(false,10);
System.out.println(a.add(b));
}
@Test
public void test8(){
Fraction a = new Fraction(false,10);
Fraction b = new Fraction(false,10);
System.out.println(a.sub(b));
}
@Test
public void test9(){
System.out.println(CalculateService.prior("+", "-"));
}
@Test
public void test10(){
getExpression getExpression = new getExpression();
List exp = getExpression.getExp(new OperationExpression(null, null, null), 3);
System.out.println(CalculateService.toPostFix(exp));
}
}
运行结果
运行test1
查看Exersises.txt,成功生成题目:
查看Answers.txt,答案:
复制一份存为Answers1.txt,修改其中0,4,6号的答案,运行test2
项目小结
这次的结对编程,让我们认识到了团队的力量,刚开始的时候效率其实不高,但后面经过不断讨论交流,结对编程这种方式也展现出它高效的一面。还是感觉写代码前的工作没有做到位,如果能把每个人的工作安排到位,编程效率应该可以更高。而且这次的查重思路并没有完全解决问题。