【coding.net地址】:https://git.coding.net/aspirinone/2016011995week2.git
【测试】在git上clone项目以后,在命令行编译为class文件,再运行java Main 10
【计划】项目完成步骤
【开发过程】
一、需求分析
(1) 程序可接收一个输入参数n,然后随机产生n道加减乘除练习题,每个数字在 0 和 100 之间,运算符在3个到5个之间。
(2) 每个练习题至少要包含2种运算符,而且练习题在运算过程中不得出现负数与非整数,比如不能出 3÷5+2=2.6,2-5+10=7等算式。
二、功能设计
基本要求:根据需求随机出题并得出答案。
三、设计实现
(1)Main.java类启动程序,接收参数n,引用ChuangJian.java
(2) ChuangJian.java类创建一个result.txt文件(如果已有一个txt文件,要事先删除),输出学号和题目,引用ChuTi.java类
1 public class Main { 2 public static void main(String[] args) { 3 Scanner input = new Scanner(System.in); 4 System.out.print("请输入题目数量:"); 5 int n = 0; 6 try{ 7 n = input.nextInt(); 8 }catch (Exception e){ 9 Scanner in = new Scanner(System.in); 10 System.out.print("输入有误,请输入数字:"); 11 n = in.nextInt(); 12 } 13 ChuangJian.File(n); 14 } 15 }
1 public class ChuangJian { 2 public static void File(int n){ 3 try{ 4 File file = new File("result.txt"); 5 if (file.exists()) { 6 file.delete(); 7 } 8 if(file.createNewFile()){ 9 FileOutputStream txtfile = new FileOutputStream(file); 10 PrintStream p = new PrintStream(txtfile); 11 p.println("2016011995"); 12 for(int i=0;i<n;i++){ 13 p.println(ChuTi.crePro()); 14 } 15 txtfile.close(); 16 p.close(); 17 System.out.println("文件创建成功!"); 18 } 19 } 20 catch(IOException ioe) { 21 ioe.printStackTrace(); 22 } 23 } 24 }
(3)ChuTi.java类中随机产生3-5个操作符,并随机产生相应的数字,随机产生的操作符是通过下标数组确定的:(部分代码)
1 public static String crePro(){ 2 Random random = new Random(); 3 String[] operator = {"+","-","×","÷"}; 4 5 int operatorCount = 3+random.nextInt(3); //操作符的个数3-5 6 int[] num = new int[operatorCount+1]; 7 int[] index = index(operatorCount); //操作符的下标 8 String s = new String(); 9 }
1 private static int[] index(int n){ //产生操作符的下标数组 2 Random random = new Random(); 3 int similar=0; 4 int[] a = new int[n]; 5 for(int j=0;j<n;j++){ 6 a[j] = random.nextInt(4); 7 } 8 for(int j=1;j<n;j++){ 9 if(a[0]==a[j]) similar++; 10 } 11 if(similar==n-1) return index(n); 12 else { 13 return a; 14 } 15 }
(4)Jisuan.java类用于判断运算过程中是否出现负数和小数,使用了调度场算法和逆波兰表达式,详见算法详解部分。
四、算法详解
在Jisuan.java类中运用了以下思想:
参考了博客《调度场算法与逆波兰表达式》:https://blog.csdn.net/acdreamers/article/details/46431285
调度场算法规则如下:从左到右遍历中缀表达式的每个数字和符号,若是数字就输出,即成为后缀表达式的一部分;若是符号,则判断其与栈顶符号的优先级,若是右括号或优先级低于栈顶符号,则弹出栈 顶元素并输出,将当前元素压入栈,一直到输出后缀表达式为止。
逆波兰表达式求值步骤:
*初始化一个空堆栈
*如果字符是一个操作数,把他压入堆栈
*如果字符是个操作符,弹出两个操作数,执行恰当操作,然后把结果压入堆栈,如果不能够弹出两个操作数,那么后缀表达式的语法错误
*到后缀表达式末尾,从堆栈中弹出结果,若后缀表达式格式正确,那么堆栈应该为空
(1)使用HashMap键值对规定字符优先级:
1 public static int calculate(String s) { 2 Stack<Integer> stack1 = new Stack<>(); //放数字 3 Stack<String> stack2 = new Stack<>(); //放操作符 4 HashMap<String, Integer> hashmap = new HashMap<>(); //存放运算符优先级 5 hashmap.put("(", 0); 6 hashmap.put("+", 1); 7 hashmap.put("-", 1); 8 hashmap.put("×", 2); 9 hashmap.put("÷", 2);
(2)对字符串中的操作符一个个处理,遇到数字则压入栈,calculate()函数用于将运算过程中出现负数和小数的情况都返回为负数,后续排除。
1 switch (c) { 2 case ‘=‘: { //遇到等号 3 String stmp; 4 while (!stack2.isEmpty()) { //当前符号栈里面还有+ - * /,即还没有算完 5 stmp = stack2.pop(); 6 int a = stack1.pop(); 7 int b = stack1.pop(); 8 int sresulat = calculate(b, a, stmp); 9 if(sresulat<0) 10 return -1; 11 stack1.push(sresulat); 12 } 13 break; 14 } 15 16 default: { //遇到加减乘除等操作符 17 String stmp; 18 while (!stack2.isEmpty()) { //如果符号栈有符号 19 stmp = stack2.pop(); //当前符号栈,栈顶元素 20 if (hashmap.get(stmp) >= hashmap.get(String.valueOf(c))) { //比较优先级 21 int a = stack1.pop(); 22 int b = stack1.pop(); 23 int sresulat =calculate (b, a, stmp); 24 if(sresulat<0) 25 return -1; 26 stack1.push(sresulat); 27 } 28 else { 29 stack2.push(stmp); 30 break; 31 } 33 } 34 stack2.push(String.valueOf(c)); //将符号压入符号栈 35 break; 36 } 37 }
【测试】
【时间分配】