1、实验目的与要求
(1)掌握软件项目个人开发流程。
(2)掌握Coding.net上发布软件项目的操作方法。
2、实验内容和步骤
任务1:
使用JAVA编程语言,独立完成一个3到5个运算符的四则运算练习的软件。
软件基本功能要求如下:
- 程序可接收一个输入参数n,然后随机产生n道加减乘除(分别使用符号+-*÷来表示)练习题,每个数字在 0 和 100 之间,运算符在3个到5个之间。(我设置的运算符都是4个)
- 每个练习题至少要包含2种运算符。同时,由于小学生没有分数与负数的概念,你所出的练习题在运算过程中不得出现负数与非整数,比如不能出 3÷5+2=2.6,2-5+10=7等算式。(完成)
- 练习题生成好后,将你的学号与生成的n道练习题及其对应的正确答案输出到文件“result.txt”中,不要输出额外信息,文件目录与程序目录一致。(完成)
- 当程序接收的参数为4时,以下为一个输出文件示例。(完成)
2018010203
13+17-1=29
11*15-5=160
3+10+4-16=1
15÷5+3-2=4
软件附加功能要求如下:(请有余力的同学完成)
- 支持有括号的运算式,包括出题与求解正确答案。注意,算式中存在的括号数必须大于2对,且不得超过运算符的个数。(完成)
- 扩展程序功能支持真分数的出题与运算(只需要涵盖加减法即可),例如:1/6 + 1/8 + 2/3= 23/24。注意在实现本功能时,需支持运算时分数的自动化简,比如 1/2+1/6=2/3,而非4/6,且计算过程中与结果都须为真分数。(之后会加以补充)
3.psp时间表:
PSP2.1 | 任务内容 | 计划共完成需要的时间(min) | 实际完成需要的时间(min) |
Planning | 计划 | 30 | 45 |
· Estimate | · 估计这个任务需要多少时间,并规划大致工作步骤 | 30 | 45 |
Development | 开发 | 310 | 615 |
· Analysis | · 需求分析 (包括学习新技术) | 60 | 120 |
· Design Spec | · 生成设计文档 | 20 | 30 |
· Design Review | · 设计复审 (和同事审核设计文档) | 10 | 10 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 10 | 10 |
· Design | · 具体设计 | 30 | 45 |
· Coding | · 具体编码 | 160 | 300 |
· Code Review | · 代码复审 | 10 | 60 |
· Test | · 测试(自我测试,修改代码,提交修改) | 10 | 40 |
Reporting | 报告 | 30 | 60 |
· Test Report | · 测试报告 | 10 | 30 |
· Size Measurement | · 计算工作量 | 10 | 10 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 10 | 20 |
4.测试用例
5.部分method的介绍:
(1)运算规则
/**
* 设置运算符相对应的运算规则
* @param a 传入的第一个参数
* @param b 传入的第二个参数
* @param op 传入的符号
* @return 返回运算结果
*/
public static Integer compute(int a, int b, String op){
switch(op){
case Const.Operator.add:
return a+b;
case Const.Operator.sub:
return a-b;
case Const.Operator.mul:
return a*b;
case Const.Operator.div:
return a/b;
default: return null;
}
}
/***
* @method: 限定运算符
*/
public static class Const {
public interface Operator{
String add = "+";
String div = "÷";
String sub = "-";
String mul = "*";
}
}
(2)算符的优先级
//用map来定义运算符的优先级
static{
map.put("+", 1);
map.put("-", 1);
map.put("*", 2);
map.put("÷", 2);
map.put("" , -1);
}
(3)逆波兰算法 (之前没有用到过,在别人的博客里看到了很简单易懂的算法描述https://www.cnblogs.com/hantalk/p/8734511.html)
中缀表达式a + b*c + (d * e + f) * g,其转换成后缀表达式则为a b c * + d e * f + g * +。转换过程需要用到栈,具体过程如下:
1)如果遇到操作数,我们就直接将其输出。
2)如果遇到操作符,则我们将其放入到栈中,遇到左括号时我们也将其放入栈中。
3)如果遇到一个右括号,则将栈元素弹出,将弹出的操作符输出直到遇到左括号为止。注意,左括号只弹出并不输出。
4)如果遇到任何其他的操作符,如(“+”, “*”,“(”)等,从栈中弹出元素直到遇到发现更低优先级的元素(或者栈为空)为止。弹出完这些元素后,才将遇到的操作符压入到栈中。有一点需要注意,只有在遇到" ) "的情况下我们才弹出" ( ",其他情况我们都不会弹出" ( "。
5)如果我们读到了输入的末尾,则将栈中所有元素依次弹出。
/***
* @method preTosuffix :将中序字符串转换为后序字符串
* @param expree 传入的字符串
* @return 通过StringBuffer存入后缀表达顺序的字符
* @throws Exception
*/
public static String preToSuffix(String expree)throws Exception{
String[] expression = expree.split(",");
StringBuffer suffixStr = new StringBuffer();
String temp = "";
/*for (int i = 0; i < expression.length; i++) {
System.out.println("--->"+expression[i]);
}*/
try{
for(int i = 0; i < expression.length; i++){
if(SufToValue.isNum(expression[i])){
suffixStr.append(expression[i]).append(" "); //如果是数字,将数字与空格等依次存入字符串缓存区的过程
}else if(expression[i].equals(")")){
temp = stack.pop();
while(!temp.equals("(")){ //如遇到右括号,将栈顶元素依次pop出,直到遇到左括号停止
suffixStr.append(temp).append(" ");
temp = stack.pop();
}
}else if(expression[i].equals("(")|| map.get(expression[i])>=map.get(getFirstOp())){ //遇到(和后面的运算的优先级更高,那么将前一个元素push进栈,先计算后面
stack.push(expression[i]);
}else{ //输入的末尾则将栈中所有的元素全部输出
temp = stack.pop();
suffixStr.append(temp).append(" ").append(expression[i]).append(" ");
}
}
}catch (Exception e){
throw e;
}
while(stack.size() > 0){
suffixStr.append(stack.pop());
}
return suffixStr.toString();
}
(4) 根据得到的后缀表达式进行相应的计算处理,得到随机生成的算式的结果
/***
*
* @param expression 接受来自perToSuffix()的return 的结果,即用空格分割的后缀表达式
* @return 将算数运算的最终结果返回
* @throws Exception 不能整除 或者 结果为负数时,抛出相应异常
*/
public static int compute(String expression) throws Exception {
int numOne, numTwo;
String temp = "";
String[] express = expression.split(" ");
Stack<String> stack = new Stack<>();
/* for (int i = 0; i < express.length; i++) {
System.out.println("---->" +express[i]);
}*/
for (int i = 0; i < express.length; i++){
/*System.out.println("ok");
System.out.println(express[i]);
System.out.println(isNum(express[i]));*/
if (isNum(express[i])){//是数字则入栈 {1 4 8 16}
stack.push(express[i]);
System.out.println(express[i]);
/*String str2 = stack.peek();
System.out.println(str2);*/
} else//若遇见运算符,则开始进行相应的计算//
{
numTwo = Integer.parseInt(stack.pop());
numOne = Integer.parseInt(stack.pop());
temp = compute(numOne, numTwo, express[i]).toString();
if (Integer.parseInt(temp) < 0){ //负数的情况 抛出异常
throw new Exception("Negative numbers in operation");
}
if(express[i].equals(Const.Operator.div)){ //做除法运算的时,若不能整除,则抛出异常
if (numOne % numTwo != 0){
throw new Exception("Counts appear in operations");
}
}
stack.push(temp); //将每一次的运算结果入栈,以备下一次运算的使用
}
}
return Integer.parseInt(stack.pop());
}
(5)生成相应的随机数和运算符,并将算式写入文件中
/***
* @method generate: 生成随机数以及运算符, 得到相应的结果并将最终的算式写入文件
* @return true: 操作成功 false: 操作失败
*/
public static boolean generate(){
while(true){
/***
* 产生四个随机数
*/
a = random.nextInt(100);
b = random.nextInt(100);
c = random.nextInt(100);
d = random.nextInt(100);
firOp = OPTERATE[random.nextInt(4)];
secOp = OPTERATE[random.nextInt(4)];
thiOp = OPTERATE[random.nextInt(4)];
//为保证运算中至少有两个不同运算 则需要做以下判断
if(firOp.equals(secOp)&& firOp.equals(thiOp)){
continue;
}
String infixExpression = Combination(firOp, secOp, thiOp, a, b, c, d);
try{
//调用方法得到最终结果
result = SufToValue.compute(InfToSuf.preToSuffix(infixExpression));
}catch (Exception e){
continue;
}
if(result>0){
StringBuffer buffer = new StringBuffer();
String[] str = infixExpression.split(",");
for(int i=0; i<str.length; i++){
buffer.append(str[i]);
}
//把算式组合到一起
buffer.append(" = ").append(result).append("\n");
if(writeToFile(buffer.toString())){
return true;
}else return false;
}
}
}
(6)计算分数中用到的方法
FractionCal(int a, int b)//含参的构造器,来对分数的分子分母进行处理
//对分子分母是否符合运算规则进行判定 对分子分母进行约分
void setNumeratorAndDenominator(int a, int b) { // 设置分子和分母
int c = f(Math.abs(a), Math.abs(b)); // 计算最大公约数
numerator = a / c;
denominator = b / c;
if (numerator < 0 && denominator < 0) {
numerator = -numerator;
denominator = -denominator;
}
}
int f(int a, int b) 求最大公约数
public static String[] compute(String data1, String consequence)//对分数进行计算
StringTokenizer类用来处理分数。 String.split用于分割字符串。也可以使用正则表达式的Pattern和Mather类来处理字字符串.
6.心得体会:
先说我再项目中收获到了什么吧!在看学长学姐们的作业之后,我感觉到我最开始的设想存在着许多的问题,比如如何去解决算式的运算问题。虽然之前碰见过,用c语言写这样的小demo的时候,只知道可以使用堆栈去解决,然后自己曾经也做出来过,但是没有总结方法,回想起来,可能之前的算法是存在问题。于是去看我不懂的算法,去学习相应的知识。同时也简单地学习了正则表达式如何使用。到最后加入分数的计算的时候,又重新写了针对分数的运算的一大堆代码,使代码显得比较冗余,没有用到同一套“+-*/”方法也是其中的一个比较大的问题。
当使用命令行去运行java项目时,也遇到了不少的问题:1.命令行的默认编码是gbk,用编译器写出来的java项目一般是UTF-8。 2. 当项目分布在不同的包时,找不到主函数的情况。在学习用命令行的过程中,发现网上写的一些东西有时候并不适用于自己的情况,需要耐心地去寻找答案。
如何使用github/coding也同样困扰着我,我还在思考为什么上传项目不能够直接将文件拖到相应的框中即上传成功,还需要用到cmd
我认识到,我在学习的过程中,落下了许多的东西,总以为事情的原理的很简单,这件事情就很简单,于是以为自己会的,动起手来却束手束脚,不知从何下手,像无头苍蝇一样,找不到方向。习惯写完了所有的代码之后再去测试,那样产生的问题会很多,容易走很多弯路,找到错误所在,需要不断的debug,发生无限循环、if中的条件丢三落四等情况会耗费掉许多的时间。我发现程序员其实也是“哲学家” ,当它不知道怎么办的时候,找不到bug的时候,他会思考人生,有种“风雨欲来,山之将倾”的感觉。但生活还得继续,于是又回到写程序上来。
我发现我上手次数不多,导致自己代码不够规范,可读性有待提高,在之后的学习中应该增强动手的能力,让自己在煎熬中快速成长起来!
github: https://github.com/huxiwen/Homework-softwarePro1