项目成员:黄思扬(3117004657)、刘嘉媚(3217004685)
二、PSP表格
PSPPersonal 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)怎么生成算式并且查重?
生成的算式要求不能产生负数、生成的题目不能重复,且即任何两道题目不能通过有限次交换+和×左右的算术表达式变换为同一道题目,经过讨论,决定使用二叉树来实现,由于二叉树的运算次序是孩子结点的运算次序要优先于根结点的,所以使用非叶子节点存放运算符,叶子结点存放数值,可以解决前后符号之间的优先级别关系,解决括号的添加问题,当父母结点的算术符优先级高于右孩子结点的算术运算符时,左右都要加括号,当相等时,则右孩子树要加括号;出现了负数时,交换左右子树即可;此外,解决了查重的复杂度问题,开始的方案想采用遍历的方式来达到查重,现只需要判断两棵树是否相同即可。
五、程序关键代码
从页面获取到数据,进行处理:
packageservice;importjava.util.HashMap;importjava.util.Map;public classEntryJudge{public booleanEntry(String[] args){//向主页面返回的运行成功与否的标志
boolean tag = false;//判断用户输入是否正确
if(args.length == 0 || args.length % 2 != 0) {
tag= false;returntag;
}//取出参数
Map params =checkParams(args);//执行相应处理
CreateAth opera = newCreateAth(params);
Judge check= newJudge(params);if(params.containsKey("-e")&¶ms.containsKey("-a")){
check.Judge();
tag= true;returntag;
}else if(params.containsKey("-n") || params.containsKey("-r") || params.containsKey("-d")) {
opera.createAth();
tag= true;returntag;
}returntag;
}private MapcheckParams(String[] args) {
Map params = new HashMap<>();for (int i = 0; i < args.length; i = i + 2) {
params.put(args[i], args[i+1]);
}returnparams;
}
}
EntryJudge
题目生成逻辑处理:
packageservice;importcom.sun.org.apache.xalan.internal.xsltc.compiler.util.StringStack;importpo.Deposit;importpo.Fraction;importutil.FileUtil;importpo.ChildDeposit;importjava.util.ArrayList;importjava.util.List;importjava.util.Map;importjava.util.Stack;importjava.util.concurrent.ExecutorService;importjava.util.concurrent.Executors;importjava.util.concurrent.ThreadLocalRandom;importjava.util.concurrent.TimeUnit;/*** 生成题目类*/
public classCreateAth{private int maxNum = 100; //生成题目的整数最大值
private int denArea = 20; //分母的范围
private int maxCount = 10;//生成题目数量
privateDeposit content;private static final String[] SYMBOLS = newString[]{"+", "-", "x", "\u00F7"};/*** 生成随机题目,初始化,把主类中输入的参数内容调进来*/
public CreateAth(Mapparams) {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 voidcreateAth() {
StringBuilder exercises= newStringBuilder();
StringBuilder answers= newStringBuilder();
List 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(intarea) {
ThreadLocalRandom random=ThreadLocalRandom.current();int x =random.nextInt(area);if (x == 0) x = 1;returnx;
}/*** ThreadLocalRandom类在多线程环境中生成随机数。
* nextBoolean() 方法用于从随机数生成器的序列返回下一个伪随机的,均匀分布的布尔值*/
private booleanrandomBoolean() {
ThreadLocalRandom random=ThreadLocalRandom.current();returnrandom.nextBoolean();
}/*** 把生成的每个数进行处理得出分子分母*/
privateFraction creator() {if(randomBoolean()) {return new Fraction((random(maxNum)), 1);
}else{if(randomBoolean()) {int den =random(denArea);int mol = random(den *maxNum);return newFraction(den, mol);
}else{int den =random(denArea);return newFraction(random(den), den);
}
}
}/*** 单步计算
*@paramsymbol 符号
*@paramleft 左
*@paramright 右
*@return得出来结果后经过约分的分数*/
privateFraction calculate(String symbol, Fraction left, Fraction right) {switch(symbol) {case "+":returnleft.add(right);case "-":returnleft.subtract(right);case "x":returnleft.multiply(right);default:returnleft.divide(right);
}
}/*** 随机生成一道四则运算题目
*@paramfractionNum 运算符个数
*@return二叉树*/
private Deposit build(intfractionNum){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);returnnode;
}/*** 获取表达式,
* 打印题目与答案*/
privateString print(){return print(content) + " = " +content.getValue();
}privateString print(Deposit node){if (node == null){return "";
}
String frac=node.toString();
String left=print(node.getLeft());if (node.getLeft() instanceof ChildDeposit && node instanceofChildDeposit) {if(bracketsLeft(((ChildDeposit) node.getLeft()).getSymbol(), ((ChildDeposit) node).getSymbol())) {
left= "(" + " " + left + " " + ")";
}
}
String right=print(node.getRight());if (node.getRight() instanceof ChildDeposit && node instanceofChildDeposit) {if(bracketsRight(((ChildDeposit) node.getRight()).getSymbol(), ((ChildDeposit) node).getSymbol())) {
right= "(" + " " + right + " " + ")";
}
}return left + frac +right;
}/*** 比较两个符号谁优先级更高,子树的箱号优先级低要加括号,左括号or右括号*/
private booleanbracketsLeft(String left,String mid){return (left.equals("+")|| left.equals("-")) && (mid.equals("x")||mid.equals("\u00F7"));
}private booleanbracketsRight(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(booleanisBuild){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);
}
}
}/*** 查重*/@Overridepublic booleanequals(Object o) {if (this == o) return true;if (!(o instanceof CreateAth)) return false;
CreateAth exercise=(CreateAth) o;returncontent.equals(exercise.content);
}
Fraction getResult() {returncontent.getValue();
}
——————以下是判断题目所要调用到的方法———————————/*** 中缀表达式生成树,用栈的特点,把中缀表达式变成前缀表达式
* 在判错中调用
*@paramexercise 中缀表达式
*@return二叉树*/Deposit build(String exercise) {
String[] strs= exercise.trim().split(" "); //拿走标号
Stack 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(newFraction(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();returncontent;
}/*** 将符号压入节点栈且计算结果,仅在生成前缀表达式*/
private void push(String symbol, StacknodeStack) {
Deposit left=nodeStack.pop();
Deposit right=nodeStack.pop();
ChildDeposit node= newChildDeposit(symbol, left, right);
node.setValue(calculate(symbol, left.getValue(), right.getValue()));
nodeStack.push(node);
}
}
CreateAth
题目判错处理:判断答案的正确性,并记录下来正确题目与错误题目序号,打印到Grade.txt
packageservice;importpo.Fraction;importutil.FileUtil;importjava.util.ArrayList;importjava.util.List;importjava.util.Map;/*** 答案判错类*/
public classJudge{private int trueNum; //正确数目
private int wrongNum; //错误数目
private String exerciseFileName; //题目文件名
private String answerFileName; //答案文件名
public Judge(Mapparams) {for(String str : params.keySet()) {if (str.equals("-e")) {
exerciseFileName=params.get(str);
}else if (str.equals("-a")) {
answerFileName=params.get(str);
}
}
}/*** 判断错误 ,并把错误写入文件*/
public voidJudge() {long start =System.currentTimeMillis();
List correctNums = new ArrayList<>();
List 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 correctNums, ListwrongNums) {
StringBuilder builder= newStringBuilder();
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");returnbuilder.toString();
}
}
Judge
分数处理类:把所有随机生成的数都当成是分数处理,同时在些定义分数的四则运算方法
packagepo;/*** 1. 把所有随机生成的数都当成是分数处理(解决了自然整数,分数,带分数之间的差异)
* 2. 定义了分数的四则运算类*/
public classFraction{private int mol; //分子
private int den; //分母
/*** 处理随机生成的数值(约分等),组合分数对象*/
public Fraction(int mol, intden) {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;
}/*** 处理随机生成的数值,这个用于分解分数对象(仅在判错中使用)*/
publicFraction(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;
}
}/*** 定义加减乘除类,返回值类型(全都当成分数处理),由于要返回这个类的内容,所以方法前要加类名*/
publicFraction add(Fraction fraction) {return new Fraction(this.mol * fraction.den + this.den * fraction.mol, this.den *fraction.den);
}publicFraction subtract(Fraction fraction) {return new Fraction(this.mol * fraction.den - this.den * fraction.mol, this.den *fraction.den);
}publicFraction multiply(Fraction fraction) {return new Fraction(this.mol * fraction.mol, this.den *fraction.den);
}publicFraction divide(Fraction fraction) {return new Fraction(this.mol * fraction.den, this.den *fraction.mol);
}
}
Fraction
题目实现存放类以及其子类:
packagepo;importjava.util.Objects;/*** 用于存放题目的类,用二叉树的形式*/
public classDeposit{privateDeposit left;privateDeposit right;private Fraction value; //用于二叉树结点的是符号与运算结果数值的之间的变化
publicDeposit(Fraction value, Deposit left, Deposit right){this.value =value;this.left =left;this.right =right;
}/*** 取结点数据*/
publicFraction getValue() {returnvalue;
}publicDeposit getRight(){returnright;
}publicDeposit getLeft(){returnleft;
}/*** 设置结点数据*/
publicDeposit(Fraction value){this.value =value;
}public voidsetLeft(Deposit left){this.left =left;
}public voidsetRight(Deposit right){this.right =right ;
}public voidsetValue(Fraction value) {this.value =value;
}
@OverridepublicString toString() {returnvalue.toString();
}/*** 用于查重,判断二棵树是否相同*/@Overridepublic booleanequals(Object o) {if (this == o) return true;if (!(o instanceof Deposit)) return false;
Deposit node=(Deposit) o;return Objects.equals(value, node.value) &&Objects.equals(left, node.left)&&Objects.equals(right, node.right);
}
}
—————————————————————————————————————分割线——————————————————————————————————————————————packagepo;/*** 用于记录符号结点,与Deposit类是一样的道理*/
public class ChildDeposit extendsDeposit{privateString symbol;publicChildDeposit(String symbol, Deposit left, Deposit right){super(null, left, right);this.symbol =symbol;
}publicString getSymbol() {returnsymbol;
}
@OverridepublicString toString() {return " " + symbol + " ";
}/*** 用于查重*/@Overridepublic booleanequals(Object o) {if (this == o) return true;if (!(o instanceof ChildDeposit)) return false;
ChildDeposit that=(ChildDeposit) 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("+") || this.symbol.equals("x")) {
left= this.getLeft() != null &&getLeft().equals(that.getRight());
right= this.getRight() != null &&getRight().equals(that.getLeft());
}return left &&right;
}
}
Deposit,ChildDeposit
文件读写工具类:处理文件的写入与读出
packageutil;import java.io.*;public classFileUtil{/*** 写入文件中*/
public static voidwriteFile(String content, String fileName) {
File file= newFile(fileName);try (BufferedWriter bw = new BufferedWriter(newFileWriter(file))){if(!file.exists()){
file.createNewFile();
}
bw.write(content);
bw.flush();
}catch(IOException e) {
System.out.println("文件操作失败...");
}
}/*** 读文件内容
*@paramcallBack 回调接口,分别处理每一行
*@paramexerciseFileName 题目文件
*@paramanswerFileName 答案文件*/
public static voidreadFile(ReaderCallBack callBack, String exerciseFileName, String answerFileName) {
File exerciseFile= newFile(exerciseFileName);
File answerFile= newFile(answerFileName);if(!exerciseFile.exists() || !answerFile.exists()) {
System.out.println("文件不存在!");return;
}try (BufferedReader br1 = new BufferedReader(newFileReader(exerciseFileName));
BufferedReader br2= new BufferedReader(newFileReader(answerFileName))) {
String line1, line2;while ((line1 = br1.readLine()) != null && (line2 = br2.readLine()) != null) {
callBack.deal(line1, line2);
}
}catch(IOException e) {
System.out.println("读取文件失败!");
}
}public interfaceReaderCallBack {void deal(String exercise, String answer) throwsIOException;
}
}
FileUtil
图形界面:
MainView :主页面,用户在两种操作中选一进行
packageview;import javax.swing.*;import java.awt.*;importjava.awt.event.ActionEvent;importjava.awt.event.ActionListener;public classMainView {public static voidmain(String[] args) {newMainJFrame();
}
}class MainJFrame extendsJFrame {/*** 程序主界面*/
private static final long serialVersionUID = 1;//定义全局变量
privateJLabel title;privateJButton generate,judge;privateJLabel result;private JPanel down = newJPanel();//创建一个容器
Container ct;
MainJFrame(){
ct=this.getContentPane();this.setLayout(null);//设置容器为空布局,绝对定位//标题
title= new JLabel("四则运算题目生成程序");
title.setFont(new Font("微软雅黑",Font.BOLD, 30));
title.setBounds(140, 40, 340, 100);//生成
generate = new JButton("生成题目");
generate.setBounds(120, 220, 140, 40);
generate.addActionListener(newgenerateListener());//判错
judge = new JButton("判断对错");
judge.setBounds(300, 220, 140, 40);
judge.addActionListener(newjudgeListenner());//添加组件
ct.add(title);
ct.add(generate);
ct.add(judge);
ct.add(down);this.setTitle("MyApp");this.setSize(600, 450);//设置窗口大小
this.setLocationRelativeTo(null);//基本设置 把窗口位置设置到屏幕中心
this.setVisible(true);//显示窗口
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); //关闭窗口 当点击窗口的关闭按钮时退出程序(没有这一句,程序不会退出)
}class generateListener implementsActionListener {//监听生成按钮点击事件
public voidactionPerformed(ActionEvent e) {newGenerateView();
}
}class judgeListenner implementsActionListener{//监听判错按钮点击事件
public voidactionPerformed(ActionEvent e) {newJudgeView();
}
}
}
MainView
GenerateView:用户输入参数范围、生成题目的页面
packageview;importservice.EntryJudge;import javax.swing.*;import java.awt.*;importjava.awt.event.ActionEvent;importjava.awt.event.ActionListener;public class GenerateView extendsJFrame {privateJLabel title,result;privateJButton confirm;privateJLabel subjectNum,intArea,denArea;privateJTextField subjectNumField,intAreaField,denAreaField;private JPanel down = newJPanel();publicGenerateView() {//创建一个容器
Container ct;
ct=this.getContentPane();//this.setLayout(null);//设置容器为空布局,绝对定位
this.setSize(600, 450);//基本设置
this.setLocationRelativeTo(null);this.setLayout(null);
title= new JLabel("生成题目");
title.setFont(new Font("微软雅黑",Font.BOLD, 20));
title.setBounds(200,20,280,60);//生成题目数
subjectNum = new JLabel("请输入生成题目数:");
subjectNum.setFont(new Font("微软雅黑",Font.BOLD, 16));
subjectNum.setBounds(70,100,160,50);
subjectNumField= new JTextField (10);
subjectNumField.setBounds(260,100,260,40);//整数范围
intArea = new JLabel("请输入整数范围:");
intArea.setFont(new Font("微软雅黑",Font.BOLD, 16));
intArea.setBounds(70,150,160,50);
intAreaField= new JTextField (20);
intAreaField.setBounds(260,150,260,40);//分母范围
denArea = new JLabel("请输入分数分母的范围:");
denArea.setFont(new Font("微软雅黑",Font.BOLD, 16));
denArea.setBounds(70,200,180,50);
denAreaField= new JTextField (20);
denAreaField.setBounds(260,200,260,40);
confirm= new JButton("确定");
confirm.setBounds(250,270, 60, 50);
confirm.addActionListener(newConfirmActionListener());//设置底部panel
down.setBounds(130, 330, 280, 50);//设置底部panel背景透明
down.setBackground(null);
down.setOpaque(false);
result= newJLabel();
result.setFont(new Font("微软雅黑",Font.BOLD, 18));//添加组件
down.add(result);
ct.add(title);
ct.add(subjectNum);
ct.add(subjectNumField);
ct.add(intArea);
ct.add(intAreaField);
ct.add(denArea);
ct.add(denAreaField);
ct.add(confirm);
ct.add(down);this.setVisible(true);//显示窗口
this.setLocationRelativeTo(null);//基本设置 把窗口位置设置到屏幕中心
this.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); //关闭窗口
}class ConfirmActionListener implementsActionListener {public voidactionPerformed(ActionEvent e) {
String args1=subjectNumField.getText();
String args2=intAreaField.getText();
String args3=denAreaField.getText();
String [] args= new String[]{"-n",args1,"-r",args2,"-d",args3};
EntryJudge ej= newEntryJudge();boolean res =ej.Entry(args);//获取命令执行结果
if(res == true) {
result.setText("结果:生成题目成功");
}else{
result.setText("结果:生成题目失败");
}
}
}
}
GenerateView
JudegeView:用户选择文件,是判断对错的页面
packageview;importservice.EntryJudge;import javax.swing.*;import java.awt.*;importjava.awt.event.ActionEvent;importjava.awt.event.ActionListener;public class JudgeView extendsJFrame {privateJLabel title,result;privateJButton confirm;privateJLabel subjectNum,intArea,file1,file2;privateJButton btn1,btn2;private JPanel down = newJPanel();publicJudgeView() {//创建一个容器
Container ct;
ct=this.getContentPane();this.setSize(600, 450);//基本设置
this.setLocationRelativeTo(null);this.setLayout(null);
title= new JLabel("判断对错");
title.setFont(new Font("微软雅黑",Font.BOLD, 20));
title.setBounds(200,20,280,60);//生成题目数
subjectNum = new JLabel("请选择题目文件:");
subjectNum.setFont(new Font("微软雅黑",Font.BOLD, 16));
subjectNum.setBounds(70,100,160,50);
btn1= new JButton ("选择文件");
btn1.setBounds(240,100,120,40);
btn1.addActionListener(newChooseFile1ActionListener());
file1= newJLabel();
file1.setFont(new Font("微软雅黑",Font.BOLD, 14));
file1.setBounds(390,100,130,50);//整数范围
intArea = new JLabel("请输入答案文件:");
intArea.setFont(new Font("微软雅黑",Font.BOLD, 16));
intArea.setBounds(70,150,160,50);
btn2= new JButton ("选择文件");
btn2.setBounds(240,150,120,40);
btn2.addActionListener(newChooseFile2ActionListener());
file2= newJLabel();
file2.setFont(new Font("微软雅黑",Font.BOLD, 14));
file2.setBounds(390,150,130,50);
confirm= new JButton("确定");
confirm.setBounds(250,270, 60, 50);
confirm.addActionListener(newConfirmActionListener());//设置底部panel
down.setBounds(130, 330, 280, 50);//设置底部panel背景透明
down.setBackground(null);
down.setOpaque(false);
result= newJLabel();
result.setFont(new Font("微软雅黑",Font.BOLD, 18));//添加组件
down.add(result);
ct.add(title);
ct.add(subjectNum);
ct.add(btn1);
ct.add(file1);
ct.add(intArea);
ct.add(btn2);
ct.add(file2);
ct.add(confirm);
ct.add(down);this.setVisible(true);//显示窗口
this.setLocationRelativeTo(null);//基本设置 把窗口位置设置到屏幕中心
this.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); //关闭窗口
}class ChooseFile1ActionListener implementsActionListener {public voidactionPerformed(ActionEvent e) {
JFileChooser chooser= new JFileChooser(); //设置选择器
chooser.setMultiSelectionEnabled(true); //设为多选
int returnVal = chooser.showDialog(new JLabel(),"选择");
String filename= chooser.getSelectedFile().getName(); //获取绝对路径
file1.setText(filename);
}
}class ChooseFile2ActionListener implementsActionListener {public voidactionPerformed(ActionEvent e) {
JFileChooser chooser2= new JFileChooser(); //设置选择器
chooser2.setMultiSelectionEnabled(true); //设为多选
int returnVal = chooser2.showDialog(new JLabel(),"选择");
String filename= chooser2.getSelectedFile().getName(); //获取绝对路径
file2.setText(filename);
}
}class ConfirmActionListener implementsActionListener {public voidactionPerformed(ActionEvent e) {//获取结果
String [] args = new String[]{"-e", "Exercises.txt","-a","Answers.txt"};
EntryJudge ej= newEntryJudge();boolean res =ej.Entry(args);//获取命令执行结果
if(res == true) {
result.setText("结果:生成题目成功");
}else{
result.setText("结果:生成题目失败");
}
}
}
}
JudegeView
六、测试测试结果
程序运行,成功打开图形界面:
测试题目生成是否成功:
输入相关参数,点击确定 ,生成题目成功:
查看Exersises.txt,成功生成题目:
查看Answers.txt,答案:
测试判错程序:
改错二道题的答案:
打开判错的图形界面,选择算式文件、选择答案文件:
点击确定,查看统计文件Grade.txt:
生成一万道算式:
详细的github地址:
部分截图:
七、项目小结
此次的结对编程,我们均涉及了代码的设计、编写以及测试。讨论项目的基本构思对我负责的前期编码十分重要,从基础数值的定义和生成,再到采用二叉树存放算式,最后一步步完成项目,通过两个人的讨论,使得项目的整体思路变得清晰。在后期的编码中我的伙伴对我的编程方式提出了更有条理性的建议,最终在小改动下让整个项目的代码变得更为整洁与条理。
多讨论交流意见对编程的帮助很大,在判错功能中,从开始的没有头绪,到讨论出来通过应用一个栈的实例:中缀表达式转前缀表达式,解决了判错的问题;开始项目生成大数量题目的速度很慢,在小伙伴丰富的编程经验下,指出了关于多线程运行程序的方式,并且通过优化一些代码,大大提高了效率。