任务1源码可直接克隆的仓库地址:
(HTTPS)https://git.coding.net/zhaoliguaner/Calculate.git (SSH)git@git.coding.net:zhaoliguaner/Calculate.git
需求分析
使用JAVA编程语言,独立完成一个3到5个运算符的四则运算练习的命令行软件开发。
功能分析
一、基本功能
- 程序可接收一个输入参数n,然后随机产生n道加减乘除(分别使用符号+-*÷来表示)练习题,每个数字在 0 和 100 之间,运算符在3个到5个之间。
- 为了让小学生得到充分锻炼,每个练习题至少要包含2种运算符。同时,由于小学生没有分数与负数的概念,你所出的练习题在运算过程中不得出现负数与非整数,比如不能出 3÷5+2=2.6,2-5+10=7等算式。
- 练习题生成好后,将你的学号与生成的n道练习题及其对应的正确答案输出到文件“result.txt”中,不要输出额外信息,文件目录与程序目录一致。
- 当程序接收的参数为4时,以下为一个输出文件示例。
二、附加功能
- 支持有括号的运算式,包括出题与求解正确答案。注意,算式中存在的括号必须大于2个,且不得超过运算符的个数。
- 扩展程序功能支持真分数的出题与运算(只需要涵盖加减法即可),例如:1/6 + 1/8 + 2/3= 23/24。注意在实现本功能时,需支持运算时分数的自动化简,比如 1/2+1/6=2/3,而非4/6,且计算过程中与结果都须为真分数。
设计实现
设计了以下函数:
1. LinkedList<String> expression() 产生运算式
2. int[ ] operator() 产生随机操作符
3. int decide(int x,int y) 通过递归实现整除
4. int transferToPostfix(LinkedList<String> list,int n,String[] ss) 将中缀表达式转化为后缀表达式
5. int calculate(int n,String[] ss) 计算后缀表达式
6. boolean isOperator(String oper) 进行操作符的判断
7. int priority(String s) 进行操作符优先级排序
8. int cal(int num1,int num2,String operator) 进行数字间的运算
9. void printHeader() 将结果输出到文件
通过上述函数的使用,目前只完成了基本功能,对于增加括号和分数的功能尚未实现。
显示部分代码
/**
* 产生运算符,个数及种类,并限制种类大于2。此处采用递归
* @return int[] ope 返回生成的运算符
*/
private static int[] operator() {
Random random = new Random();
boolean flag = false;
int n=random.nextInt(3)+3; //3-5个运算符,运算符个数
ope=new int[n];
for (int j=0;j<n;j++) ope[j]=random.nextInt(4); //随机选择某个运算符
for (int j = 1; j < n; j++) { //控制运算符种类
if(ope[0]!=ope[j]) flag = true;
}
if (!flag) {
operator();
}
return ope;
}
/**
* 中缀表达式转化为后缀表达式
* @param LinkedList<String> list
* @param int n
* @param String[] ss
* @return int m
*/
private static int transferToPostfix(LinkedList<String> list,int n,String[] ss){
Iterator<String> it=list.iterator();
while (it.hasNext()) {
String s = it.next();
if (isOperator(s)) {
if (operators.isEmpty()) {
operators.push(s);
}
else {
//如果读入的操作符为非")"且优先级比栈顶元素的优先级高或一样,则将操作符压入栈
if (priority(operators.peek())<priority(s)&&!s.equals(")")) {
operators.push(s);
}
else if(!s.equals(")")&&priority(operators.peek())>=priority(s)){
while (operators.size()!=0&&priority(operators.peek())>=priority(s)&&!operators.peek().equals("(")) {
if (!operators.peek().equals("(")) {
String operator=operators.pop();
sb.append(operator).append(" ");
output.push(operator);
}
}
operators.push(s);
}
//如果读入的操作符是")",则弹出从栈顶开始第一个"("及其之前的所有操作符
else if (s.equals(")")) {
while (!operators.peek().equals("(")) {
String operator=operators.pop();
sb.append(operator).append(" ");
output.push(operator);
}
//弹出"("
operators.pop();
}
}
}
//读入的为非操作符
else {
sb.append(s).append(" ");
output.push(s);
}
}
if (!operators.isEmpty()) {
Iterator<String> iterator=operators.iterator();
while (iterator.hasNext()) {
String operator=iterator.next();
sb.append(operator).append(" ");
output.push(operator);
iterator.remove();
}
}
int m = calculate(n,ss);
sb.delete(0,sb.length());
return m ;
}
/**
* 计算后缀表达式并输出到文件
* @param int n
* @param String[] ss
* @return int n
*/
private static int calculate(int n,String[] ss){
LinkedList<String> mList=new LinkedList<>();
String[] postStr=sb.toString().split(" ");
for (String s:postStr) {
if (isOperator(s)){
if (!mList.isEmpty()){
int num1=Integer.valueOf(mList.pop());
int num2=Integer.valueOf(mList.pop());
if (s.equals("/")&&(num1==0||num1>num2)){
n--;
return n;
}
if(s.equals("-")&&num2%num1!=0){
n--;
return n;
}
int newNum=cal(num2,num1,s);
mList.push(String.valueOf(newNum));
}
}
else {
//数字则压入栈中
mList.push(s);
}
}
String content = "";
if (!mList.isEmpty()){
for (int j = 0; j < ss.length; j++) {
content+=ss[j];
}
content+="="+mList.pop()+"\r\n";
}
if(!file.exists()) file = new File("result.txt");
try {
FileOutputStream fos = new FileOutputStream(file,true);//建立文件流,用以输出计算式
fos.write(content.getBytes());
fos.flush();
fos.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return n;
}
主函数:
public static void main(String[] args) {
Scanner scanner=new Scanner(System.in);
int num;
num = scanner.nextInt();
int n=0;
printHeader();
while (n<num) {
LinkedList<String> list=new LinkedList<>();
list=new CalCu().expression();
Iterator<String> it=list.iterator();
StringBuilder sd=new StringBuilder();
while (it.hasNext()) {
sd.append(it.next()).append(" ");
}
String[] ss=sd.toString().split(" ");
n = transferToPostfix(list,n,ss);
n++;
}
System.out.println("输出成功");
scanner.close();
}
测试运行
项目总结
在分析了模块化的定义(模块化程序设计即模块化设计,属于计算机编程,简单地说就是程序的编写不是开始就逐条录入计算机语句和指令,而是首先用主程序、子程序、子过程等框架把软件的主要结构和流程描述出来,并定义和调试好各个框架之间的输入、输出链接关系。)之后,我在写代码的过程中,推开之前逐条去写代码的习惯,采用了一种新的方法,定义了一些子方法,并在主程序或子程序中去调用,运用这样的编程方法,觉得代码思路清晰了很多。
PSP展示
PSP |
任务内容 |
计划共完成需要的时间(min) |
实际完成需要的时间(min) |
Planning |
计划 |
8 |
6 |
· Estimate |
· 估计这个任务需要多少时间,并规划大致工作步骤 |
8 |
6 |
Development |
开发 |
198 |
481 |
· Analysis |
需求分析 (包括学习新技术) |
30 |
120 |
· Design Spec |
· 生成设计文档 |
5 |
5 |
· Design Review |
· 设计复审 (和同事审核设计文档) |
5 |
3 |
· Coding Standard |
· 代码规范 (为目前的开发制定合适的规范) |
3 |
3 |
· Design |
· 具体设计 |
15 |
40 |
· Coding |
· 具体编码 |
80 |
120 |
· Code Review |
· 代码复审 |
20 |
40 |
· Test |
· 测试(自我测试,修改代码,提交修改) |
40 |
150 |
Reporting |
报告 |
9 |
6 |
· Test Report |
· 测试报告 |
3 |
2 |
· Size Measurement |
· 计算工作量 |
2 |
1 |
· Postmortem & Process Improvement Plan |
· 事后总结, 并提出过程改进计划 |
3 |
3 |
后记:自己之前也有敲代码,但是那些代码都比较固定,在算法上几乎没有难度,而这次的四则运算,在算法方面的要求比之前要高,所以,对我还是一个蛮大的挑战,在起初看完题目要求后,真的是觉得无从下手。在敲代码的过程中也不怎么顺,很多java的类、方法自己知道的也不多,都是在这过程当中学习的;还有相关算法(调度场算法),文件流的输出这些也是认真读了博客学习的。将代码写好测试无误后,发现还需要在命令行窗口进行编译运行,这也不同于之前的在IDE中进行编译运行,怎么办呢?再学呗。最后,又学习了git的相关知识,将项目传到encoding.net上面,并作了博客记录,整个工作才算完成。通过这次作业,我确实收获了很多,完成之后,感觉十分欣慰,因为我知道自己收获满满。而且,我发现自己现在养成了想要去记录博客的好习惯,很开心,技术和习惯上的双丰收。所以,想要感谢老师,感谢这道题,也感谢自己,谢谢。