第一次结对编程作业
211606330 王建木 211606345 翟堂贵
一、预估与实际
PSP2.1 | Personal Softwar Process Stages | Estimated Time(hours) | Practical Time(hours) |
Planning | 计划 | 20 | 24 |
|
|
20 | 24 |
Development | 开发 | 18 | 22 |
|
|
3 | 3 |
|
|
3 | 3 |
|
|
0.5 | 0.5 |
|
|
0.5 | 0.5 |
|
|
5 | 7 |
|
|
4 | 6 |
|
|
1 | 1 |
|
|
2 | 2 |
Reporting | 报告 | 2 | 2 |
|
|
1 | 1 |
|
|
0.5 | 0.5 |
|
|
0.5 | 0.5 |
二、需求分析
我通过网上查询了解到,小学三年级数学的四则运算特点如下:
- 运算符在2~4个,可以加括号,一对括号算一个运算符,"="不算运算符
- 减法运算的结果不能为负数
- 除法运算不能有余数,除数不能为零
- 算式内最多只能有一对括号,并且括号不能无意义
- 能在命令行模式下正常运行
- 算式结果小于10000
- 需要保留之前1-2年级出题器的功能
- 输入格式为-n 题目数 (1-100) -grade 年级(1-3)或-grade 年级(1-3)-n 题目数(1-100)
- 生成题目后需要将题目和答案保存到out.txt文件中并打印输出
三、设计
1.设计思路
- 设计getThirdGradeMathSubject方法,根据随机生成的运算符的个数(2-4)分别调用handleTwo、handleThree、handleFour方法处理
- 判断是否需要添加括号,若为true,则调用getBrackets方法返回添加完括号的运算符串;
- 根据是否添加括号分别调用priotitySort和prioritySortWithoutBrackets方法分别对传入的运算符串进行优先级排序,分别返回排序完成后带括号和不带括号的运算符串
- 遍历第三步生成的运算符串,循环调用handleSign方法,根据当前遍历到的运算符(+,-,×,÷)进行不同的处理。handleSign方法接收如("+",100)的参数,生成第二个操作数和运算后的结果,并将操作数和结果用整形数组保存并返回。当传入的运算符为"÷"时调用getFactor方法保证操作数2为传入的操作数1的因数,保证除法运算的结果为整数。
- 遍历结束后将得到的操作数数组和排序完成的运算符数组重新排列,生成算式和答案并返回即可。
2.计算关键点
- 计算关键点:如何将运算符串进行优先级排序。------>定义三个空栈,遍历运算符串,当栈为空时将运算符压入栈1栈顶。当栈1不为空时,将当前运算符与栈顶元素的优先级进行比较,若当前运算符的优先级高于栈顶元素,则将当前运算符压入栈1栈顶。反之,则循环遍历栈1,将优先级等于或高于当前运算符的元素压入栈2,循环结束后将当前运算符压入栈1栈顶,并将栈2元素压回栈1。当出现括号时,若为左括号,则直接压入栈1栈顶,若为右括号,则将左括号之前的元素压入栈3栈顶。---->栈3存储括号内的运算符。当运算符串遍历结束后,先将栈2内剩余的元素压回栈1,再将栈3内元素压回栈1即可。此时,栈1内元素的优先级为栈顶---->栈底逐渐递减。--->见关键代码。
- 技术关键点:如何在运算符串中加入括号。---->见关键代码。
四、编码
1.调试日志
2.关键代码
给运算符串添加括号
public static String getBrackets(String isNeedBrackets){ String returnString=""; List<String> list=new ArrayList<String>(); for(int i=0;i<isNeedBrackets.length();i++){ list.add(isNeedBrackets.substring(i,i+1)); } Random r=new Random(); // k表示插入左括号的位置,p表示插入右括号的位置 int k=0,p=0; k=r.nextInt(isNeedBrackets.length()); list.add(k,"("); List<Integer> rightSignLocation=new ArrayList<Integer>(); int rightLoction=0; if(k==0){ for(int i=2;i<list.size();i++){ rightSignLocation.add(i); } rightLoction=r.nextInt(rightSignLocation.size()); p=rightSignLocation.get(rightLoction); }else{ for(int i=k+2;i<=list.size();i++){ rightSignLocation.add(i); } rightLoction=r.nextInt(rightSignLocation.size()); p=rightSignLocation.get(rightLoction); } list.add(p,")"); for(String s:list){ returnString=returnString+s; } return returnString; }
将运算符串进行排序,并返回括号
// 将传入的运算符字符串进行排序,即令运算符按高→低排列,结果带括号 public static String[] prioritySort(String[] signString){ String currentElement=""; String[] newString=null; List<String> stringList=new ArrayList<String>(); // 定义两个空栈 Stack<String> strStack1=new Stack<String>(); Stack<String> strStack2=new Stack<String>(); Stack<String> strStack3=new Stack<String>(); for(int i=0;i<signString.length;i++){ currentElement=signString[i]; // 遍历字符串,当栈为空时,直接将字符压入栈1 if(strStack1.empty() || currentElement.equals("(")){ strStack1.push(currentElement); }else{ if(currentElement.equals(")")){ strStack3.push(currentElement); while(!strStack1.peek().equals("(")){ strStack3.push(strStack1.pop()); } // 将"("弹出并压入栈2 strStack3.push(strStack1.pop()); }else { /* 判断字符优先级,若当前字符的优先级高于栈1 * 栈顶字符的优先级,则将当前字符压入栈1栈顶; * 反之,则将栈1栈顶元素出栈,压入栈2栈顶, * 并循环判断当前字符与栈顶元素的优先级; */ if(getPriority(currentElement,strStack1.peek())){ strStack1.push(currentElement); }else{ // 注意循环时应该先判断栈是否空,否则会引起越界问题 strStack2.push(strStack1.pop()); while(!strStack1.empty() && !strStack1.peek().equals("(") && !getPriority(currentElement,strStack1.peek())){ strStack2.push(strStack1.pop()); } strStack1.push(currentElement); while(!strStack2.empty()){ strStack1.push(strStack2.pop()); } } } } } while(!strStack3.empty()){ strStack1.push(strStack3.pop()); } while(!strStack1.empty()){ if(strStack1.peek().equals("(")){ strStack1.pop(); stringList.add(")"); }else if(strStack1.peek().equals(")")){ strStack1.pop(); stringList.add("("); }else{ stringList.add(strStack1.pop()); } } newString=new String[stringList.size()]; for(int i=0;i<stringList.size();i++){ newString[i]=stringList.get(i); } return newString; }
将运算符串进行排序,不返回括号
// 将传入的运算符字符串进行排序,即令运算符按高→低排列,不返回括号 public static String[] prioritySortWithoutBrackets(String[] signString){ String currentElement=""; String[] newString=null; List<String> stringList=new ArrayList<String>(); // 定义两个空栈 Stack<String> strStack1=new Stack<String>(); Stack<String> strStack2=new Stack<String>(); Stack<String> strStack3=new Stack<String>(); for(int i=0;i<signString.length;i++){ currentElement=signString[i]; // 遍历字符串,当栈为空时,直接将字符压入栈1 if(strStack1.empty() || currentElement.equals("(")){ strStack1.push(currentElement); }else{ if(currentElement.equals(")")){ while(!strStack1.peek().equals("(")){ strStack3.push(strStack1.pop()); } // 将"("弹出 strStack1.pop(); }else { /* 判断字符优先级,若当前字符的优先级高于栈1 * 栈顶字符的优先级,则将当前字符压入栈1栈顶; * 反之,则将栈1栈顶元素出栈,压入栈2栈顶, * 并循环判断当前字符与栈顶元素的优先级; */ if(getPriority(currentElement,strStack1.peek())){ strStack1.push(currentElement); }else{ // 注意循环时应该先判断栈是否空,否则会引起越界问题 strStack2.push(strStack1.pop()); while(!strStack1.empty() && !strStack1.peek().equals("(") && !getPriority(currentElement,strStack1.peek())){ strStack2.push(strStack1.pop()); } strStack1.push(currentElement); while(!strStack2.empty()){ strStack1.push(strStack2.pop()); } } } } } while(!strStack3.empty()){ strStack1.push(strStack3.pop()); } while(!strStack1.empty()){ if(strStack1.peek().equals("(")){ strStack1.pop(); stringList.add(")"); }else if(strStack1.peek().equals(")")){ strStack1.pop(); stringList.add("("); }else{ stringList.add(strStack1.pop()); } } newString=new String[stringList.size()]; for(int i=0;i<stringList.size();i++){ newString[i]=stringList.get(i); } return newString; }
3.代码规范
- 类名使用UpperCamelCase风格
- 方法名、参数名、成员变量、局部变量都统一使用 lowerCamelCase 风格
- 不允许任何魔法值(即未经预先定义的常量)直接出现在代码中
- 左小括号和字符之间不出现空格;同样的,有小括号和字符之间也不出现空格
- if/for/while/switch/do等保留字与括号之间都必须加空格
- 大括号的使用约定。如果是大括号内为空,则简介地写成{}即可,不需要换行;如果是非空代码块则:
- 左大括号前不换行
- 左大括号后换行
- 右大括号前换行
- 右大括号后还有 else 等代码则不换行;表示终止的右大括号后必须换行
- 任何二目、三木运算符的左右两边都需要加一个空格
- 方法参数在定义和传入是,多个参数逗号后边必须加空格
五、测试
测试编号 | 具体输入 | 预期结果 | 实际结果 |
1 | -n 10 -grade 0 | 请输入正确的年级(1-3) | 符合预期 |
2 | -n 10 -grade 3 | 正常执行,生成10道三年级四则运算题 | 符合预期 |
3 | -n 1000 -grade 3 | 请输入正确的题目数(1-100) | 符合预期 |
4 | -grade 3 -n 10 | 正常执行,生成10道三年级乘除运算题 | 符合预期 |
5 | -grade 4 -n 10 | 请输入正确的年级(1-3) | 符合预期 |
六、总结
- 博客别等代码写完了在写。我认为好的博客应该记录下自己编程过程中遇到的问题,并提出解决的方案。在编程中很多的bug都是之前设计的时候没有想到的,对这些bug的解决方案都是很有意义的,能提醒自己不要再犯类似的错误,应该都记录下来,如果等到代码写完了在写博客,会丢失掉很多编程过程中的bug解决方案(我的调试日志是空的🙂)。(千万要记住)
- 在程序结构设计的时候,一定要把握整体的流程。不要一直埋头写代码,应该先想清楚自己现在写的这部分代码在整个程序中起到什么作用,写完这部分代码会对接下来要写的其他部分有什么影响,如果一直写代码,会经常发现你解决完目前的问题,在这基础上又会跑出新的问题,而这些问题很有可能会让你大量返工,不断修改之前写的代码,浪费大量的时间和精力。就如上面关键代码中的对运算符串进行排序时需要分带括号和不带括号两种情况,而我一开始只写了不带括号的,导致下一步的遍历出现了数组越界等等问题,花费了大量时间debug。(千万要记住)
- 这是一篇失败的博客。