任务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. MakeFraction() 实现分数的出题与计算
实现了基本功能以及附加功能(产生括号和真分数计算)在括号的产生方法上,还存在一些不足之处。
显示部分代码
/**
* 产生运算符,个数及种类,并限制种类大于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()+"
";
}
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 LinkedList<String> expression(){ char[] operator=new char[]{'+','-','*','/'}; Random random=new Random(); LinkedList<String> expression=new LinkedList<String>(); int ope[]= operator(); //产生的运算符的个数 int[] number=new int[ope.length+1]; //运算数的个数,该数组存储运算数 for(int j=0;j<=ope.length;j++){ number[j]=random.nextInt(100)+1; //4-5个数字,产生数字 //此时产生的数字是[1,10],不包括0,可以这样写,random.nextInt(20)%11 //改成限制的!!!!! } int bracketnum=random.nextInt(ope.length-1); //随机产生括号的个数,题目限定括号个数小于运算符个数 // System.out.println("括号数:"+bracketnum); if (bracketnum>0) { //产生括号 int [] lbracketloc=new int[bracketnum]; int [] rbracketloc=new int[bracketnum]; int [] leftnum=new int[ope.length+1]; //记录每个数前的左括号的数量 int [] rightnum=new int[ope.length+1]; //记录每个数后的右括号的数量 for (int i = 0; i <bracketnum; i++) { lbracketloc[i]=random.nextInt(ope.length); //随机产生左括号的位置,此左括号在第几个运算数前面,运算数不包括最后一个数, rbracketloc[i]=random.nextInt(ope.length)+1;//随机产生右括号的位置,此右括号在第几个运算数后面,运算数不包括第一个数 if (rbracketloc[i]<=lbracketloc[i]) { //若右括号的位置在左括号前面或左右两个括号只括住一个数字,则去掉本次产生的括号 i--; //这个方法只是初次保证在一次循环下产生的左右括号不括住一个数字,但仍有可能出现这种情况 } //在分次循环时,仍有可能出现括住一个数字的情况 } for (int i = 0; i < bracketnum; i++) { //利用桶函数的思想,记录每个运算数对应的括号的个数 leftnum[lbracketloc[i]]++; rightnum[rbracketloc[i]]++; } for (int i = 0; i < ope.length+1; i++) { // 再次进行限制,保证左右括号不是只括住一个数字。对一个数进行检查,当其左右两边的括号数相等时, if (!(leftnum[i]==0||rightnum[i]==0)) { // 就将它两边的括号都删掉,这样只是对单个数进行了成功的限制,但若将一个表达式作为整体看待, while (!(leftnum[i]==0||rightnum[i]==0)) { //还是会出现这种情况 leftnum[i]--; rightnum[i]--; } } } // if (!(leftnum[0]==0||rightnum[ope.length]==0)) { //对式子最前面和最后面的括号进行限制,不许出现括住整体式子的情况 // while (!(leftnum[0]==0||rightnum[ope.length]==0)) { //这样有错误 // leftnum[0]--; // rightnum[ope.length]--; // } // } int right=0; //记录加进式子的右括号的数量 int left=0; // 记录加进式子的左括号的数量 for (int i = 0; i < ope.length; i++) { for (int j = 0; j < leftnum[i]; j++) { expression.add("("); left++; } expression.add(String.valueOf(number[i])); for (int j = 0; j < rightnum[i]; j++) { expression.add(")"); right++; } expression.add(String.valueOf(operator[ope[i]])); if(ope[i]==3){ number[i+1]=decide(number[i],number[i+1]); //此处为整除的初步控制,控制可能会无效,因为在某些情况下 //加上括号后,整体的运算整个都变了,要在计算过程中去二次控制整除 } //在下面不加括号的处理中,也会因为运算顺序的改变而出现控制无效的情况 } //所以这段代码只能初步控制整除 expression.add(String.valueOf(number[ope.length])); for (int i = right; i < left; i++) { expression.add(")"); } } else { for(int i=0;i<ope.length;i++){ expression.add(String.valueOf(number[i])); expression.add(String.valueOf(operator[ope[i]])); if(ope[i]==3){ number[i+1]=decide(number[i],number[i+1]); } } expression.add(String.valueOf(number[ope.length])); } // System.out.println(expression); return expression; }
混合产生真分数和整数式子
public void Generate(int num){ //执行整数计算
Random random = new Random();
CalCu cu = new CalCu();
int n=0;
while (n<num) {
int choose = random.nextInt(3);
// System.out.println("suijishu:"+choose);
if(choose==0||choose==1){
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 = cu.transferToPostfix(list,n,ss);
n++;
}
else {
cu.MakeFraction();
n++;
}
}
}
测试运行
项目总结
在分析了模块化的定义(模块化程序设计即模块化设计,属于计算机编程,简单地说就是程序的编写不是开始就逐条录入计算机语句和指令,而是首先用主程序、子程序、子过程等框架把软件的主要结构和流程描述出来,并定义和调试好各个框架之间的输入、输出链接关系。)之后,我在写代码的过程中,推开之前逐条去写代码的习惯,采用了一种新的方法,定义了一些子方法,并在主程序或子程序中去调用,运用这样的编程方法,觉得代码思路清晰了很多。
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上面,并作了博客记录,整个工作才算完成。通过这次作业,我确实收获了很多,完成之后,感觉十分欣慰,因为我知道自己收获满满。而且,我发现自己现在养成了想要去记录博客的好习惯,很开心,技术和习惯上的双丰收。所以,想要感谢老师,感谢这道题,也感谢自己,谢谢。