2016012047+小学四则运算练习软件项目报告
代码仓库地址:https://git.coding.net/dingxs/2016012047dingxsSoftWare.git
一、需求分析:
1.程序可接收一个输入参数n,然后随机产生n道加减乘除(分别使用符号+-*÷来表示)练习题,每个数字在 0 和 100 之间,运算符在3个到5个之间。
2.为了让小学生得到充分锻炼,每个练习题至少要包含2种运算符。同时,由于小学生没有分数与负数的概念,你所出的练习题在运算过程中不得出现负数与非整数,比如不能出 3÷5+2=2.6,2-5+10=7等算式。
3.练习题生成好后,将学号与生成的n道练习题及其对应的正确答案输出到文件“result.txt”中,不要输出额外信息,文件目录与程序目录一致。
二、功能设计:
1.可提示用户选择自己想要进行计算的位数,可选择4,5,6。
2.程序根据用户输入的数据n可生成n个运算式,可提示用户输入1--1000。
3.可自动输出result。
4.将其输出到result.txt中。
三、设计实现:
1.Main类调用了Produce类中的produce方法,CreaFile中的creatFile方法。
2.Produce类中produce()方法用于随机产生混合四则运算式,calculate()方法用于计算。此处用到后缀表达式。
3.CreaFile用于输出文件。
4.Produce.priority()方法用于计算操作符的优先级;
详解produce方法:
基本算法思想:先随机生成n个数字(假设n=4),将其存入一个数组,然后随机出'+','-','*','/',将第一个随机出的运算符号插在第一个数字后面,也就是将后面的三个后移,定义一个head,一个tail,我们发现1是四号位置,2是六号位置,3是八号位置,于是依据规律得出结论:int head=(i+1)*2;int tail=len+1;总体思想就是先产生数字,再后移数字,随机产生运算符,插入运算符。
四、代码详解
1.main处理非法字符,并且调用方法,就是一个入口。
package com.neneu.dingxs1; import java.io.IOException; public class Main { public static void main(String[] args) throws IOException{ int n = 0; try{ n=Integer.parseInt(args[0]); }catch(Exception e){ System.out.println("请输入合法字符"); return; } Produce.produce(); CreaFile.creatFile(n); } }
2.produce方法,用于随机产生混合四则运算。先产生数字,再后移数字,随机产生运算符,插入运算符。
public static String produce() { System.out.println("请输入你想进行计算的位数,可选4,5,6"); Scanner sc = new Scanner(System.in); int ss = sc.nextInt(); System.out.println("请输入你想要输出的式子数:"); int s = sc.nextInt(); int i = 0; String[] array = new String[100]; while(s-->0) { Random ran1 = new Random(); int len=0; for(i=0;i<ss;i++) { len++; array[i]=String.valueOf(ran1.nextInt(100)+1); } char[] ch = {'+','-','*','/'}; for(i=0;i<ss-1;i++){ int index=ran1.nextInt(ch.length); int head=(i+1)*2; int tail=len+1; for(int j=tail;j>=head;j--) { array[j]=array[j-1]; } array[head-1]=String.valueOf(ch[index]); len++; } LinkedList<String> list=new LinkedList<String>(); for(i=0;i<len;i++) { list.add(array[i]); } transferToPostfix(list); for(i=0;i<len;i++) { System.out.print(array[i]); } System.out.print("="+"\n"); } String strr = new String(); strr = array[i]+"="+"\n"; System.out.println(strr); return strr ; }
3.calculate用于计算(后缀表达式)
备注:中缀表达式到后缀表达式的转换
要把表达式从中缀表达式的形式转换成用后缀表示法表示的等价表达式,必须了解操作符的优先级和结合性。优先级或者说操作符的强度决定求值顺序;优先级高 的操作符比优先级低的操作符先求值。 如果所有操作符优先级一样,那么求值顺序就取决于它们的结合性。操作符的结合性定义了相同优先级操作符组合的顺序(从右至左或从左至右)。
转换过程包括用下面的算法读入中缀表达式的操作数、操作符和括号:
1. 初始化一个空堆栈,将结果字符串变量置空。
2. 从左到右读入中缀表达式,每次一个字符。
3. 如果字符是操作数,将它添加到结果字符串。
4. 如果字符是个操作符,弹出(pop)操作符,直至遇见开括号(opening parenthesis)、优先级较低的操作符或者同一优先级的右结合符号。把这个操作符压入(push)堆栈。
5. 如果字符是个开括号,把它压入堆栈。
6. 如果字符是个闭括号(closing parenthesis),在遇见开括号前,弹出所有操作符,然后把它们添加到结果字符串。
7. 如果到达输入字符串的末尾,弹出所有操作符并添加到结果字符串。
后缀表达式求值
对后缀表达式求值比直接对中缀表达式求值简单。在后缀表达式中,不需要括号,而且操作符的优先级也不再起作用了。您可以用如下算法对后缀表达式求值:
1. 初始化一个空堆栈
2. 从左到右读入后缀表达式
3. 如果字符是一个操作数,把它压入堆栈。
4. 如果字符是个操作符,弹出两个操作数,执行恰当操作,然后把结果压入堆栈。如果您不能够弹出两个操作数,后缀表达式的语法就不正确。
5. 到后缀表达式末尾,从堆栈中弹出结果。若后缀表达式格式正确,那么堆栈应该为空。
private static void calculate(){ 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()); //除数为0 if (s.equals("/")&&num1==0){ System.out.println("ohmygod"); return; } //整除 /* if(s.equals("/")&&(num1/num2!=0)) { num2 = (int) (Math.random() * 20) + 1; num1 = (int) (Math.random() * 6) * num2; }*/ //避免出现负数 if(s.equals("-")&&(num2>num1)) { int t = num1; num1=num2; num2=t; } int newNum=cal(num2,num1,s); mList.push(String.valueOf(newNum)); } } else { //数字则压入栈中 mList.push(s); } } if (!mList.isEmpty()){ System.out.println("result: "+mList.pop()); //String output = mList.pop(); } }
4.判断是否是操作符
private static boolean isOperator(String oper){ if (oper.equals("+")||oper.equals("-")||oper.equals("/")||oper.equals("*") ||oper.equals("(")||oper.equals(")")) { return true; } return false; }
5.计算操作符的优先级
private static int priority(String s){ switch (s) { case "+":return 1; case "-":return 1; case "*":return 2; case "/":return 2; case "(":return 3; case ")":return 3; default :return 0; } }
6.transferToPostfix
private static void transferToPostfix(LinkedList<String> list){ 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(); } } // System.out.println("后缀: "+sb); calculate(); //Collections.reverse(output); }
7.输出到result.txt
备注:FileOutputStream
该类用来创建一个文件并向文件中写数据。
如果该流在打开文件进行输出前,目标文件不存在,那么该流会创建该文件。
有两个构造方法可以用来创建 FileOutputStream 对象。
使用字符串类型的文件名来创建一个输出流对象:
OutputStreamf = newFileOutputStream("C:/java/hello")
也可以使用一个文件对象来创建一个输出流来写文件。我们首先得使用File()方法来创建一个文件对象:
Filef = newFile("C:/java/hello"); OutputStreamf = newFileOutputStream(f);
创建OutputStream 对象完成后,就可以使用下面的方法来写入流或者进行其他的流操作。
package com.neneu.dingxs1; import java.io.*; public class CreaFile { public static void creatFile(int n){ try{ File file = new File("result.txt"); if (file.exists()) { file.delete(); } if(file.createNewFile()){ FileOutputStream txtfile = new FileOutputStream(file); PrintStream p = new PrintStream(txtfile); p.println("2016012047"); for(int i=0;i<n;i++){ //System.out.println("!"+Produce.produce()); p.println(Produce.produce()); } txtfile.close(); p.close(); System.out.println("文件创建成功!"); } } catch(IOException ioe) { ioe.printStackTrace(); } } }
8.满意的代码片段:
public static String produce() { System.out.println("请输入你想进行计算的位数,可选4,5,6"); Scanner sc = new Scanner(System.in); int ss = sc.nextInt(); System.out.println("请输入你想要输出的式子数:"); int s = sc.nextInt(); int i = 0; String[] array = new String[100]; while(s-->0) { Random ran1 = new Random(); int len=0; for(i=0;i<ss;i++) { len++; array[i]=String.valueOf(ran1.nextInt(100)+1); } char[] ch = {'+','-','*','/'}; for(i=0;i<ss-1;i++){ int index=ran1.nextInt(ch.length); int head=(i+1)*2; int tail=len+1; for(int j=tail;j>=head;j--) { array[j]=array[j-1]; } array[head-1]=String.valueOf(ch[index]); len++; } LinkedList<String> list=new LinkedList<String>(); for(i=0;i<len;i++) { list.add(array[i]); } transferToPostfix(list); for(i=0;i<len;i++) { System.out.print(array[i]); } System.out.print("="+"\n"); }
五、运行测试
测试机为Windows环境,所有提交到Coding.net上的项目必须包含src文件夹,在src文件夹中必须包含名为Main.java文件,且Main.java中包含public static void main(String[] args)方法。同时请注意以下三点:
- 生成文件时请使用相对路径(也就是说,不要带任何C:\ 这样的路径),生成的txt 文件需在项目的根目录下,可直接查看演示示例。如何自测:在windows系统中打开命令行界面(Linux/Mac 使打开终端,后续操作一致),进入项目所在根目录下,使用 javac src/Main.java,然后再使用 java src/Main 1,生成的results.txt在当前目录下。
- 使用的JDK版本为 jdk8u161
- 使用的JRE版本为jre8u161
六、psp
PSP2.1 | 任务内容 | 计划共完成需要的时间/h | 实际完成需要的时间/h |
Planning | 计划 | 1 | 2 |
·Estimate | ·估计这个任务需要多少时间,并规划大致工作步骤 | 1 | 2 |
Development | 开发 | 14.5--19.5 | 27 |
·Analysis | ·需求分析 (包括学习新技术) | 3 | 4 |
·Design Spec | ·生成设计文档 | 0.5 | 1 |
·Design Review | ·设计复审(和同事审核设计文档) | ||
·Coding Standard | ·代码规范(为目前的开发制定合适的规范) | 1 | 2 |
·Design | ·具体设计 | 2 | 4 |
·Coding | ·具体编码 | 5--10 | 10 |
·Code Review | ·代码复审 | 1 | 1 |
·Test | ·测试(自我测试,修改代码,提交修改) | 2 | 5 |
Reporting | 报告 | 7.5 | 12 |
·Test Report | ·测试报告 | 4 | 5 |
·Size Measurement | ·计算工作量 | 0.5 | 1 |
·Postmortem & Process Improvement Plan | ·事后总结,并提出过程改进计划 | 3 | 6 |
七、测试
八、result.txt
九、个人总结
自己的编码能力比较差,敲时就觉得特别困难,花费了很长时间做出来的项目还是非常不尽人意。这次让我看到了和其他同学之间的差距,很有危机感。大家都加了括号,我有一个大概的思考,但是还没有实现,我是这样想的:数字后面可以加有括号和运算符;运算符后面可以加数字和左括号;左括号后面可以加数字和左括号;右括号后面可以加右括号和运算符;右括号和数字的情况是一样的,运算符和左括号的情况是一样的。可以放在一起考虑。最后没有实现,自己要趁着这门课程补充自己的java知识。编程能力差的感觉真的太难受了。每周一定要坚持做几个题,提高自己的编码能力。