20172303 2017-2018-2 《程序设计与数据结构》结对编程项目-四则运算
20172303 2017-2018-2 《程序设计与数据结构》结对编程项目-四则运算
结对对象
- 姓名:范雯琪
- 学号:20172303
- 第一周博客地址
- 担任角色:
- 前期:确定类的个数和每个类的基础编写
- 领航员:范雯琪
- 操作员:张昊然
- 解释:前期主要是范雯琪来确定每个类有什么功能,里面大概有什么方法,我负责先在IDEA里把大体敲出来,当遇到有问题的地方,范雯琪来查然后他做其他的。
- 中期:类的修改与优化(无明显角色差别)
- 解释:在这个阶段角色就不是很明显了,两个人把所有类平均分配分别找错误,谁找到了的话先自己解决,不能解决的话再一起讨论。
- 后期:尝试加入括号
- 领航员:张昊然
- 操作员:范雯琪
- 解释:中缀转后缀中加括号和产生题目时加括号的思路都是我想的,然后范雯琪负责把我的思路用具体的代码实现。
- 前期:确定类的个数和每个类的基础编写
需求分析
- 可自动生成题目,题目的难度和数量可由用户自己定义
- 题目支持整数、真分数和加减乘除四则运算
- 可判断用户答案是否正确并输出正确答案,最后计算用户的正确率
- 扩展需求:
- 题目去重
- 支持多种语言,语言类型可由用户自行选择
- 可使生成的题目及其正误保存在一个记事本文件中
设计思路
一、产生随机有理数
- 设计了一个运算数类,使其可以随机生成一个有理数,既可以是整数也可以是真分数。
二、生成题目
- 设计了一个生成题目类,先可以随机生成四种运算符,接着通过结合运算数和运算符生成题目。
三、计算题目
- 本来是想把真分数和整数的计算放在一起,后经张旭升学长在课上提醒第四章的例题中已经提供了可以进行分数运算的RationalNumber类。
- 设计了一个中缀转后缀类,使用逆波兰表示法。
- 设计了一个计算类,调用产生问题类和中缀转后缀类,进行题目的计算。
四、测试类
- 设计一个测试类,并能实现让用户输入题目难度和数量,判断正误并计算率。
五、UML图
相关过程及解释
中缀转后缀方法实现
import java.util.*;
public class InfixToSuffix
{
private Stack<String> stack;
private List<String> list;
private String message, Message = "";
public InfixToSuffix() {
stack = new Stack<String>(); // Store operator
list = new ArrayList<String>(); // Store operation number and operator
}
public void conversion(String expr) {
String token;
StringTokenizer tokenizer = new StringTokenizer(expr);
while (tokenizer.hasMoreTokens()) {
// If tokenizer has the next value, loop and assign value.
token = tokenizer.nextToken();
if (token.equals("(")) {
// If the value of the token is the left parenthesis, then the stack
stack.push(token);
}else if (token.equals("+") || token.equals("-")) {
// If the value of token is "+" or "-", once again determine whether the stack is empty.
if (!stack.empty()){
// If the stack is not empty, judge what is the top element of the stack
if (stack.peek().equals("(")) {
// If the top of the stack is "(", the operator enters the stack
stack.push(token);
}else{
// Otherwise, remove the stack top elements first, add them to the list,
// and then stack the operators into the stack.
list.add(stack.pop());
stack.push(token);
}
}else {
// Otherwise the operator enters the stack
stack.push(token);
}
}else if (token.equals("*") || token.equals("÷")){
// If the value of token is "*" or "÷", it again determines whether the stack is empty.
if (!stack.empty()) {
// If the stack is not empty, judge what is the top element of the stack
if (stack.peek().equals("*") || stack.peek().equals("÷")) {
// If the top of the stack is "*" or "÷", remove the stack top elements first,
// add them to the list, and then stack the operators into the stack.
list.add(stack.pop());
stack.push(token);
}else {
// In addition, the operator directly enters the stack.
stack.push(token);
}
}else {
// If the stack is empty, the operator goes directly to the stack
stack.push(token);
}
} else if (token.equals(")")) {
// If encounter "), starts to circulate
while (true) {
// Remove the top element of the stack and assign it to A
String A = stack.pop();
if (!A.equals("(")) {
// If A is not "(", it is added to the list
list.add(A);
} else {
// If A is "(", exit the loop
break;
}
}
}else {
// If it is an arithmetic number, enter the list
list.add(token);
}
}
while (!stack.empty()) {
// Remove elements from the stack and add them to the list until the stack is empty.
list.add(stack.pop());
}
ListIterator<String> li = list.listIterator();
while (li.hasNext()) {
Message += li.next() + " ";
// The elements in iterator are taken out in turn, and spaces are used as separators.
li.remove();
}
message = Message;
}
public String getMessage() {
return message;
}
}
这部分的关键点是StringTokenizer类
,是张旭升学长在某个晚自习交给我们的。
它的具体方法有:
运行过程截图
遇到的困难及解决方法
- 问题一:在测试类中如果题目数量输入“0”时仍会产生一道题,如果题目难度输入“0”则会提示错误。
- 解决方法:修改测试类,不管在哪个位置输入“0”时都会提示错误。
int j = 0;
System.out.print("请输入要生成的题目数:" );
count = number.nextInt();
while (count == 0)
{
System.out.println("错误,请输入有效数字!(最小为1,理论无上限)");
System.out.print("请输入要生成的题目数:");
count = number.nextInt();
}
System.out.print("请输入生成题目的级别(每增加一级多一个运算符,最低为一级):");
level = number.nextInt();
while (level == 0)
{
System.out.println("错误,请输入有效数字!(最小为1,理论无上限)");
System.out.print("请输入生成题目的级别(每增加一级多一个运算符,最低为一级):");
level = number.nextInt();
}
- 问题2:在产生题目时会输出多个相同的运算数和运算符
- 解决方法:采用单步调试发现是有一步多加了一遍某个“运算数+运算符”
- 问题3:在加了括号之后,产生的题目级别与实际级别不符(级别等于符号数)
- 解决方法:修改了很多遍都没有成功,询问了于欣月同学,但她们组制定级别的方式与我们不同(她们将一级设为加减,二级为乘除,三级为加减乘除)所以也没能帮我们解决问题,这个问题我们会在接下来一周中尽快解决。
对结对的小伙伴做出评价
- 范雯琪同学在我们的编程途中作出了巨大的贡献,在我无数次想要放弃的时候都是她给予了我精神上的动力,也在我遇到困难时和我一起解决一些问题,她的一些想法会让我在一些死循环中跳出,让我豁然贯通,有她真好。
- 在我们分别想问题时总爱与我讨论,有些思路被打断了...
- 在她的力争之下,我们实现了简单的,
不完美的括号运算
团队共同成果
其实在前期和中期我们基本上是各干各的或者一个人干完给另外一个人,但在加括号这部分是集中讨论最多的。
在中缀转后缀的时候为了把括号加进去列了很多遍草稿几乎把代码重新写了一遍。
而在产生题目时,我本来的思路是把左括号和右括号也作为运算符随机产生,但是如果要实现左括号一定在右括号前面且括号之内至少有两个运算数和运算符会非常麻烦。而张昊然就提出了另一种思路就是当题目产生运算符之后设定一个随机数来判断是否加括号,实现上述功能就简单了许多,在最后产生的题目能生成括号之后真的非常有成就感。
但是我们产生括号还是存在很多问题,比如因为存在括号使得级别与实际不符,而且现在我们的括号中只能放两个运算数和运算符。由于时间和个人能力问题我们没能在这周解决,但在下一周一定会把它完成的。
PSP
PSP2.1 | Personal Software Process Stages | 预估耗时(小时) | 实际耗时(小时) |
---|---|---|---|
Planning | 计划 | 0.5 | 1.5 |
Estimate | 估计这个任务需要多少时间 | 0.5 | 0.5 |
Development | 开发 | 20 | 45 |
Analysis | 需求分析 (包括学习新技术) | 2 | 2 |
Coding Standard | 代码规范 (为目前的开发制定合适的规范) | 3 | 3.5 |
Design UML | 设计项目UML类图 | 1.5 | 2 |
Coding | 具体编码 | 10 | 20 |
Code Review | 代码复审 | 2 | 2 |
Test | 测试(自我测试,修改代码,提交修改) | 2 | 2 |
Size Measurement | 计算工作量(实际时间) | 0.5 | 1 |
Postmortem & Process Improvement Plan | 事后总结, 并提出过程改进计划 | 1 | 1.5 |
合计 | 43 | 94 |