四则运算
一、摘要
作业地址:https://edu.cnblogs.com/campus/nenu/2016CS/homework/2266
git仓库地址:https://git.coding.net/kefei101/f4.git
结对成员:田佳欣 https://www.cnblogs.com/tianjx687/p/9933776.html
二、需求分析
通过对题目功能一、功能二、功能三的分析,我们共提取出以下6个需求:
1、对输入格式的判断,以及路径、提示功能的实现;
2、分别实现整数运算、整数小数运算;
3、分别实现不含括号的四则运算、含括号的四则运算;
4、对运算结果的处理分别实现保留三位小数、日常化;
5、分别实现答题计算、输出算式结果到指定txt文件;
6、分别实现表达式可重复的四则运算、表达式不重复的四则运算。
三、解题思路
我们共设计了6个类,如图:
Creat类:负责随机产生N条3个运算符,4个运算数的四则运算的式子。
Calculator类:负责计算答案。
MakeFile类:负责运算结果输出至控制台上,且保存到指定位置的txt文件中。
Symbol类:负责将产生的代表运算符的数字转换成对应的运算符。
(0:“+”;1:“-”;2:“*”;3:“/”)
Result类:负责处理不同功能要求的输出结果格式。
Main类:主类,负责接收控制台输入,并判断其输入格式,应实现的功能。
6个类的相互调用关系为:
比较重要的函数有:
Main类:main():判断输入格式以及路径、提示功能的实现,主要是逻辑。
Create类:createProblem(int n,int no):随机产生N个运算数为整数,不带括号的四则运算式子;createSm(int n):随机产生N个运算数为整数小数均可的,带有括号的四则运算式子。(n表示n个式子,no表示功能一二的选择)
Calculator类:algorithm(int[] var):计算运算数为整数,不带括号的式子具体计算算法;bracketsAlgo(int[] var):计算运算数整数小数均可 ,带括号的式子计提计算算法。(var[]代表将要存放的运算符)
MakeFile类:creatFile(int n,String filename):将N个四则运算式子输出并存放到指定文件夹。(n表示n个式子,filename表示指定txt文件路径)
Result类:treat(double res):实现不同功能对对输出结果的要求。
四、测试运行
1、输入格式错误:
2、功能一、功能二
3、功能三
五、重要代码展示
1、主方法main()测试:由于有足够的前期基础,逻辑这块没有多大难度,唯一有点难度的,就是会使用库方法获取项目路径。
1 /** 2 * Main主函数 3 * @param args args 4 */ 5 public static void main(String[] args){ 6 7 if(args.length==0){ 8 System.out.println("请进入小程序所在文件夹,并以正确的格式输入。"); 9 System.exit(1); //结束运行 10 } else if (args.length == 2) { 11 if (args[0].equals("-n")) { 12 if (args[1].matches("^[0-9]*$")) { 13 int num = Integer.parseInt(args[1]); 14 if (num <0) { 15 System.out.println("题目数量必须是 正整数。"); 16 System.exit(1); //结束运行 17 }else{ 18 Create create = new Create(); 19 create.createProblem(num, 1); 20 } 21 } else { 22 System.out.println("题目数量必须是 正整数。"); 23 System.exit(1); //结束运行 24 } 25 } else if (args[0].equals("-c")) { 26 if (args[1].matches("^[0-9]*$")) { 27 int num = Integer.parseInt(args[1]); 28 if (num > 100 || num <1) { 29 System.out.println("题目数量范围为1到100之间的正整数。"); 30 System.exit(1); //结束运行 31 }else { 32 Create create = new Create(); 33 create.createProblem(num, 2); 34 } 35 } else { 36 System.out.println("题目数量范围为1到100之间的正整数。"); 37 System.exit(1); //结束运行 38 } 39 } else { 40 System.out.println("输入格式错误!"); 41 System.exit(1); //结束运行 42 } 43 } 44 //f4 -c 题目总数 -f txt文件路径 45 else if (args.length == 4) { 46 String fileName = args[3]; 47 if (args[0].equals("-c") && args[2].equals("-f")) { 48 if (args[1].matches("^[0-9]*$")) { 49 int num = Integer.parseInt(args[1]); 50 if (num > 100 || num <1) { 51 System.out.println("题目数量范围为1到100之间的正整数。"); 52 System.exit(1); //结束运行 53 }else { 54 MakeFile makeFile = new MakeFile(); 55 makeFile.creatFile(num, fileName); 56 } 57 } else { 58 System.out.println("题目数量范围为1到100之间的正整数。"); 59 System.exit(1); //结束运行 60 } 61 } else { 62 System.out.println("输入格式错误!"); 63 System.exit(1); //结束运行 64 } 65 } else { 66 System.out.println("输入格式错误!"); 67 System.exit(1); //结束运行 68 } 69 }
2、Create类中的createProblem(int n,int no)方法和createSm(int n)方法:这个两个方法中,我们认为操作数要求是整数,结果还需要四舍五入比较难处理。由于已经确定操作符和操作数个数,在其中分别根据功能的选择调用Calculator的algorithm(opNum)和bracketsAlgo(opNum)方法,将在此输入的用户答案与计算返回的答案比较,输出。总体来说,不算太难。
1 //产生n个式子 2 for(int i=0;i) { 3 calculator = new Calculator(); 4 //初始化操作符类 5 for(int j=0;j ){ 6 opNum[j]=0; 7 } 8 //随机产生式子并计算结果 9 if(no==1) { 10 equation = calculator.algorithm(opNum); 11 }else if(no==2){ 12 equation = calculator.bracketsAlgo(opNum); 13 } 14 StringTokenizer stringTokenizer = new StringTokenizer(equation, "="); 15 ArrayList list = new ArrayList (); 16 while (stringTokenizer.hasMoreElements()) { 17 list.add(stringTokenizer.nextToken()); 18 } 19 //输出式子 20 System.out.println(list.get(0)); 21 //输入 22 System.out.print("?"); 23 Scanner scanner = new Scanner(System.in); 24 String ans = scanner.next(); 25 //判断输入是否与正确答案相符 26 if(ans.equals(list.get(1))){ 27 count++; 28 System.out.println("回答正确。"); 29 }else { 30 System.out.println("回答错误,正确答案是" + list.get(1) + "。"); 31 } 32 }
1 /** 2 *产生n个含括号的整数小数运算式式子 3 * @param n n 4 * @return return 5 */ 6 public ArrayListcreateSm(int n) { 7 8 ArrayList list = new ArrayList ();//最终 9 ArrayList list0 = new ArrayList (); 10 11 Calculator calculator = null; 12 String equation = null; 13 int operatorCount = 3;//3个操作符 14 int[] opNum = new int[operatorCount];//操作符数组 15 int count = 0; 16 int max = 0; 17 int[] len = new int[n]; 18 //产生n个式子 19 for(int i=0;i ) { 20 ArrayList list1 = new ArrayList (); 21 calculator = new Calculator(); 22 //初始化操作符类 23 for(int j=0;j ){ 24 opNum[j]=0; 25 } 26 //随机产生式子并计算结果 27 equation = calculator.bracketsAlgo(opNum); 28 list0.add(equation); 29 StringTokenizer stringTokenizer = new StringTokenizer(equation, "="); 30 while (stringTokenizer.hasMoreElements()) { 31 list1.add(stringTokenizer.nextToken()); 32 } 33 //计算最长式子的长度 34 if(max ).length()) { 35 max=list1.get(0).length(); 36 } 37 //计算每个式子的长度,存入数组,便于后续格式 38 len[i] = list1.get(0).length(); 39 } 40 //最终正确格式 41 for(int i=0;i ){ 42 ArrayList list1 = new ArrayList (); 43 StringTokenizer stringTokenizer = new StringTokenizer(list0.get(i), "="); 44 while (stringTokenizer.hasMoreElements()) { 45 list1.add(stringTokenizer.nextToken()); 46 } 47 String equ = list1.get(0) + "="; 48 //添加空格 49 for(int j = 0 ; j <8 + max - len[i]; j++){ 50 equ +=" "; 51 } 52 equ += list1.get(1); 53 list.add(equ); 54 } 55 return list; 56 }
3、Calculator类中的algorithm(opNum)方法和bracketsAlgo(opNum):这两方法是整个四则运算程序的重中之重。由于题中操作符操作数个数给定了,功能也无需很强大,因此,我们没有采用栈来计算,而是采用最原始,于我们来说容易理解的方法进行计算。其中判断以及后期的调试花了很大功夫。
1 /** 2 * 功能一:不带括号 3 * @param var var 4 * @return return 5 */ 6 public String algorithm(int[] var) { 7 Random random = new Random(); 8 int n = var.length+1;//4个操作数 9 int[] num = new int[n];//4个操作数 10 Double[] num1 = new Double[n];//4个操作数 11 int[] algo = new int[n+var.length];//存放产生四则运算数组 12 int count = 0;//运算次数 13 while(count<var.length){ 14 if(var.length == 3){ 15 var[0]=(int)(Math.random()*4); 16 var[1]=(int)(Math.random()*4); 17 var[2]=(int)(Math.random()*4); 18 } 19 for(int i=0;i){ 20 num[i]=random.nextInt(20);//随机产生4个正整数,存放在num数组中 21 } 22 for(int i=0;i ){ 23 num1[i]= Double.valueOf(num[i]);//产生的符合规定的运算符存到num1数组中 24 } 25 26 //产生的符合规定的运算符存到m1数组中,m1数组长度比字符数量多一,方便后面运算 27 int m[] = new int[var.length+1]; 28 for(int i=0;i ){ 29 m[i]=var[i]; 30 } 31 32 //将运算符跟数字交错存到数组中,输出为一个四则运算 33 algo[0]=num[0]; 34 algo[1]=var[0]; 35 for(int i=2;i ){ 36 if(i%2==0){ 37 algo[i]=num[i/2]; 38 }else 39 algo[i]=var[i/2]; 40 } 41 42 //将运算符从前到后两两比较,先计算优先级高的运算符,先乘除后加减,同级运算符从左向右依次计算 43 //优先级:0:+ 1:- 2:* 3:/ 44 for(int i=0;i ){ 45 //*优先,后加减 46 if((m[i] )){ 47 num1[i+2]=num1[i+1]*num1[i+2]; 48 num1[i+1]=num1[i]; 49 m[i+1]=m[i]; 50 count++; 51 } 52 //除优先,后加减 53 else if((m[i] ){ 54 if(num1[i+2]!=0.0){ 55 BigDecimal v1 = new BigDecimal(num1[i + 1]); 56 BigDecimal v2 = new BigDecimal(num1[i + 2]); 57 Double v3 = (v1.divide(v2,4, RoundingMode.HALF_UP)).doubleValue(); 58 num1[i+2] = v3; 59 num1[i + 1] = num1[i]; 60 m[i + 1] = m[i]; 61 count++; 62 } 63 else {//除数为0,重新进行四则运算的产生 64 count = 0; 65 for (int k = 0; k ) { 66 var[k] = 0; 67 } 68 break; 69 } 70 //先乘后除,从左至右 71 }else if (m[i] == 2 && m[i + 1] == 3) { 72 num1[i + 1] = num1[i] * num1[i + 1]; 73 count++; 74 //先加后减,从左至右 75 } else if ((m[i] )) { 76 num1[i + 1] = num1[i] + num1[i + 1]; 77 count++; 78 //先除,从左至右 79 } else if ((m[i] > m[i + 1]) && m[i] == 3) { 80 BigDecimal v1 = new BigDecimal(num1[i]); 81 BigDecimal v2 = new BigDecimal(num1[i + 1]); 82 Double v3 = (v1.divide(v2,4, RoundingMode.HALF_UP)).doubleValue(); 83 num1[i + 1] = v3; 84 count++; 85 //先乘,先加,从左至右 86 } else if ((m[i] > m[i + 1]) && m[i] == 2) { 87 num1[i + 1] = num1[i] * num1[i + 1]; 88 count++; 89 //先减,从左至右 90 } else if ((m[i] > m[i + 1]) && m[i] == 1) { 91 num1[i + 1] = num1[i] - num1[i + 1]; 92 count++; 93 //优先级相同 94 } else { 95 /* 包括情况为0==0;1==1;2==2 3==3*/ 96 if (m[i] == m[i + 1]) { 97 if (m[i] == 0) { //加法 98 num1[i + 1] = num1[i] + num1[i + 1]; 99 count++; 100 } else if (m[i] == 1) { //减法 101 num1[i + 1] = num1[i] - num1[i + 1]; 102 count++; 103 } else if (m[i] == 2) { //乘法 104 num1[i + 1] = num1[i] * num1[i + 1]; 105 count++; 106 } else { //除法运算,判断除数不能为0 107 if (num1[i + 2] != 0.0) { 108 BigDecimal v1 = new BigDecimal(num1[i]); 109 BigDecimal v2 = new BigDecimal(num1[i + 1]); 110 Double v3 = (v1.divide(v2, 4, RoundingMode.HALF_UP)).doubleValue(); 111 num1[i + 1] = v3; 112 count++; 113 } else {//除数为0,重新进行四则运算的产生 114 count = 0; 115 for (int k = 0; k ) { 116 var[k] = 0; 117 } 118 break; 119 } 120 } 121 } 122 } 123 } 124 } 125 //将符合要求的四则运算从数组中输出 ,如果为运算符,调用Symbol类中算法symbol函数,将数字转化为运算符输出 126 Symbol sym =new Symbol(); 127 String S; 128 S = String.valueOf(algo[0]) + sym.symbol(algo[1]); 129 sym.symbol(algo[1]); 130 for (int j = 2; j ) { 131 if (j % 2 == 0) 132 S = S + String.valueOf(algo[j]); 133 else { 134 S = S + sym.symbol(algo[j]); 135 } 136 } 137 Result result = new Result(); 138 String dou = result.treat(num1[num1.length - 1]); 139 S = S + "=" + dou;//输出运算结果 140 return S; 141 }
这个算法最主要的是运算符之间优先级的比较。
(1)将运算符从前到后两两比较,先计算优先级高的运算符,先乘除后加减,同级运算符从左向右依次计算。
(2)通过运算符两两比较,每循环一次将生成的结果替代原来的数字,循环结束后,最终的结果会放到数组的最后一位。若除法时,除数为0,则重新开始。
1 /** 2 * 功能二、功能三:带括号,运算数可带小数 3 * @param var var 4 * @return return 5 */ 6 public String bracketsAlgo(int[] var) { 7 Result result = new Result(); 8 Random random = new Random(); 9 //随机产生运算符 10 if (var.length == 3) { 11 var[0] = (int) (Math.random() * 4); 12 var[1] = (int) (Math.random() * 4); 13 var[2] = (int) (Math.random() * 4); 14 } 15 16 //随机生成的运算符从左向右两两比较 17 double f1 = random.nextDouble()*random.nextInt(20);//随机产生4个正数,存放在num数组中 18 double f2 = random.nextDouble()*random.nextInt(20); 19 BigDecimal n1 = new BigDecimal(f1); 20 BigDecimal n2 = new BigDecimal(f2); 21 f1 = n1.setScale(1,BigDecimal.ROUND_HALF_UP).doubleValue(); 22 f2 = n2.setScale(1,BigDecimal.ROUND_HALF_UP).doubleValue(); 23 String str = "";//算式 24 double st = 0;//当前结果 25 26 27 if(var[0]==0){ 28 st = f1+f2; 29 str = result.treat(f1) + "+" + result.treat(f2); 30 }else if(var[0]==1){ 31 st = f1-f2; 32 str = result.treat(f1) + "-" + result.treat(f2); 33 }else if(var[0]==2){ 34 st = f1*f2; 35 str = result.treat(f1) + "*" + result.treat(f2); 36 }else{ 37 while(f2==0){ 38 f2 = random.nextDouble()*random.nextInt(20); 39 } 40 BigDecimal v1 = new BigDecimal(f1); 41 BigDecimal v2 = new BigDecimal(f2); 42 Double v3 = (v1.divide(v2, 4, RoundingMode.HALF_UP)).doubleValue(); 43 st=v3; 44 str = result.treat(f1) + "/" + result.treat(f2); 45 } 46 47 for(int i=1;ii){ 48 f1 = random.nextDouble()*random.nextInt(20); 49 BigDecimal n3 = new BigDecimal(f1); 50 f1 = n3.setScale(1,BigDecimal.ROUND_HALF_UP).doubleValue(); 51 if(priority(var[i-1])<priority(var[i])){ 52 if(var[i]!=3){ 53 st *= f1; 54 str = "(" + str + ")*" + result.treat(f1); 55 }else{ 56 while(f1==0){ 57 f1 = random.nextDouble()*random.nextInt(20); 58 } 59 BigDecimal v1 = new BigDecimal(st); 60 BigDecimal v2 = new BigDecimal(f1); 61 Double v3 = (v1.divide(v2, 4, RoundingMode.HALF_UP)).doubleValue(); 62 st=v3; 63 str = "(" + str + ")/" + result.treat(f1); 64 } 65 }else if(var[i]==0){ 66 st += f1; 67 str = str + "+" + result.treat(f1); 68 }else if(var[i]==1){ 69 if(var[i - 1] == 1) { 70 st = f1 - st; 71 str = result.treat(f1) + "-(" + str + ")"; 72 }else{ 73 st -= f1; 74 str = str + "-" + result.treat(f1); 75 } 76 }else if(var[i] == 2) { 77 st *= f1; 78 str = str + "*" + result.treat(f1); 79 }else{ 80 while(f1==0){ 81 f1 = random.nextDouble()*random.nextInt(20); 82 } 83 BigDecimal v1 = new BigDecimal(st); 84 BigDecimal v2 = new BigDecimal(f1); 85 Double v3 = (v1.divide(v2, 4, RoundingMode.HALF_UP)).doubleValue(); 86 st=v3; 87 str = str + "/" + result.treat(f1); 88 } 89 } 90 str = str + "=";//输出运算结果 91 String cou = result.treat(st); 92 str +=cou; 93 return str; 94 }
这个算法也是主要比较运算符的优先级。将随机生成的运算符从左向右两两比较,如果前者优先级高,则先计算保存结果,如果前者优先级低,则加上括号。这样也是优缺点的,这种算法只能从左向右运算,无法实现更为精致的随机生成。后期我也会继续关注这算法,以求改进。
4、MakeFile类中的creatFile(int n,String filename)方法:由于底层的随机生成算式的算法已经实现了,这个方法主要是文件的创建及算式的写入。这个不会太难。
1 /** 2 * 实现将算式写入指定txt文件 3 * @param n n 4 * @param fileName fileName 5 */ 6 public void creatFile(int n,String fileName){ 7 try{ 8 File file = new File(fileName); 9 if (file.exists()) { //如果文件已存在,则删除文件 10 file.delete(); 11 } 12 if(file.createNewFile()){ 13 FileOutputStream txtFile = new FileOutputStream(file); 14 PrintStream p = new PrintStream(txtFile); 15 ArrayListlist = new ArrayList (); 16 Create create = new Create(); 17 list = create.createSm(n); 18 for(int i=0;i ){ 19 System.out.println(list.get(i)); 20 p.println(list.get(i)); 21 } 22 txtFile.close(); 23 p.close(); 24 } 25 } 26 catch(IOException ioe) { 27 ioe.printStackTrace(); 28 } 29 }
5、Result类的treat(double res)方法:这个方法我们是用来处理功能一功能二不同的输出要求的。同时针对题目中提到的“例如2/3-1/3的正确结果是0.333,而并非0.334”,我想到了题目中要求保留3位小数,那我们可以保留四位小数,在处理过程中再四舍五入,其结果无差。
1 /** 2 * 处理输出结果 3 * @param res res 4 * @return return 5 */ 6 public static String treat(double res) { 7 BigDecimal b = new BigDecimal(res); 8 res = b.setScale(3,BigDecimal.ROUND_HALF_UP).doubleValue(); 9 int aInt = (int)res; 10 String bDou = res+""; 11 String bDou1 = bDou.substring(bDou.indexOf(".")+1,bDou.length()); 12 if(bDou1.equals("0")){ 13 return String.valueOf(aInt); 14 } 15 else{ 16 return String.valueOf(res); 17 } 18 }
六、编程体会、收获事件
1、体会
在软件工程这门课中,到目前为止已经写了4次作业了。每一次写作业都会让我受益多多。我也发现自己做作业的效率一次比一次高,学习能力明显增强了,这是值得开心的事情。那对于这次作业呢,首先我觉得它是比上次wf词频统计小程序的作业难上加难,所涉及到的算法高度也不同,虽然这是结对项目,但在一起编代码的过程中也凸显了一点问题,比如说俩人意见不一,凑在一起编程的时间不多等等,在编程过程中还是有些费力的,尤其是在编写判断逻辑和后期调试花了太多的功夫。希望自己在接下来的软件工程课程上学到更多东西,在算法下功夫,不断提高编程能力,靠近预期目标。
2、事件
(1)首先我们在项目的构造上花费了2天时间,思考讨论的结果,参照我上次wf词频统计小程序的思路,一层一层逐步深入,先构造好大体逻辑结构,哪块是什么功能,再逐步细化。
(2)其次我们卡在功能一花费了3天时间,功能一实际上也是功能二和功能三的基础。式子用什么表达,怎么计算,怎么产生正确的无限长小数运算的四舍五入等等都是要有方案解决的。网上资料虽然很多,但要做到符合要求基本是没有的,而且我们想真正用我们自己想的方法把这些问题解决,真正地编程学习。
(3)最后,我们还卡在了一个非常尴尬的点上。题中要求写一个控制台程序,一开始我们以为是在控制台输入,使用scanner获取用户输入参数。但后来经过舍友提醒,又让我想起上次冉华学长对我说的话,就再去查查控制台程序输入,应该用命令行参数输入格式。可是我们试了好久,控制台直接闪退,显示输入格式错误,不知道哪里出问题,还一度怀疑是IDEA的锅。将近半天,才发现输入格式的f4不作为判断条件,它不是命令行参数中的一个。真是天大又无语的乌龙啊— —!