一、需求分析
1、项目名称:中小学数学卷子自动生成程序
2、用户:小学、初中和高中数学老师。
3、功能要求:
(1)命令行输入用户名和密码,两者之间用空格隔开(程序预设小学、初中和高中各三个账号,具体见附表),如果用户名和密码都正确,将根据账户类型显示“当前选择为XX出题”,XX为小学、初中和高中三个选项中的一个。否则提示“请输入正确的用户名、密码”,重新输入用户名、密码;
(2)登录后,系统提示“准备生成XX数学题目,请输入生成题目数量(输入-1将退出当前用户,重新登录):”,XX为小学、初中和高中三个选项中的一个,用户输入所需出的卷子的题目数量,系统默认将根据账号类型进行出题。每道题目的操作数在1-5个之间,操作数取值范围为1-100;
(3)题目数量的有效输入范围是“10-30”(含10,30,或-1退出登录),程序根据输入的题目数量生成符合小学、初中和高中难度的题目的卷子(具体要求见附表)。同一个老师的卷子中的题目不能与以前的已生成的卷子中的题目重复(以指定文件夹下存在的文件为准,见5);
(4)在登录状态下,如果用户需要切换类型选项,命令行输入“切换为XX”,XX为小学、初中和高中三个选项中的一个,输入项不符合要求时,程序控制台提示“请输入小学、初中和高中三个选项中的一个”;输入正确后,显示“”系统提示“准备生成XX数学题目,请输入生成题目数量”,用户输入所需出的卷子的题目数量,系统新设置的类型进行出题;
(5)生成的题目将以“年-月-日-时-分-秒.txt”的形式保存,每个账号一个文件夹。每道题目有题号,每题之间空一行;。
附表-1:账户、密码
账户类型 |
账户 |
密码 |
备注 |
小学 |
张三1 |
123 |
|
张三2 |
123 |
|
|
张三3 |
123 |
|
|
初中 |
李四1 |
123 |
|
李四2 |
123 |
|
|
李四3 |
123 |
|
|
高中 |
王五1 |
123 |
|
王五2 |
123 |
|
|
王五3 |
123 |
|
附表-2:小学、初中、高中题目难度要求
|
小学 |
初中 |
高中 |
|
难度要求 |
+,-,*./ |
平方,开根号 |
sin,cos,tan |
|
备注 |
只能有+,-,*./和() |
题目中至少有一个平方或开根号的运算符 |
题目中至少有一个sin,cos或tan的运算符 |
|
二、代码分析
代码一共分为三个部分:User.java,PryandSndpapers.java和Main.java。
1、User.java
1 public class User { 2 private String username; 3 private String password; 4 public HashSet<String> ph=new HashSet<String>(),jh=new HashSet<String>(),hh=new HashSet<String>(); //小学、初中、高中题目集合 5 public User(String username,String password){ 6 this.username=username; //用户名 7 this.password=password; //密码 8 } 9 public boolean isEqual(String un,String pw){ //判断用户是否匹配 10 if((username.equals(un))&&(password.equals(pw))) 11 return true; 12 return false; 13 } 14 public String getName(){ 15 return username; 16 } 17 }
此模块的优点是:自己定义了一个类,将代码模块化,使后续代码思路更加清晰。
2、PryandSndpapers.java
(1) 成员变量:
1 public class PryandSndpapers { 2 User [] g_primaryuser,g_junioruser,g_highuser; //小学、初中、高中的用户 3 boolean g_primaryflag,g_juniorflag,g_highflag; //当前出题标志位 4 String username,password; 5 User CurrentUser; //当前用户信息
将用户、标志位以及当前用户信息设置为成员变量,可以使PryandSndpapers中的所有成员函数都能够使用到这些变量。
(2) InitAccount()
1 /*----------------------------------------初始化用户信息----------------------------------------------*/ 2 public void InitAccount(){ 3 g_primaryuser=new User[]{new User("张三1","123"),new User("张三2","123"),new User("张三3","123")}; 4 g_junioruser=new User[]{new User("李四1","123"),new User("李四2","123"),new User("李四3","123")}; 5 g_highuser=new User[]{new User("王五1","123"),new User("王五2","123"),new User("王五3","123")}; 6 }
此成员函数的主要作用是初始化账户信息。
(3) CheckAccount()
1 /*--------------------------------------核对输入账户信息-----------------------------------------------*/ 2 public void CheckAccount(){ 3 boolean prime=true; //判断是否成功匹配的标志 4 int i; 5 Scanner sc=new Scanner(System.in); 6 System.out.print("请输入用户名和密码:"); 7 username=sc.next(); 8 password=sc.next(); 9 g_primaryflag=false; //初始化出题标志位 10 g_juniorflag=false; 11 g_highflag=false; 12 for(i=0;i<3;i++){ 13 if(g_primaryuser[i].isEqual(username, password)){ 14 g_primaryflag=true; 15 System.out.println("登陆成功!\n当前选择为小学出题"); 16 CurrentUser=g_primaryuser[i]; 17 prime=false; 18 break; 19 } 20 } 21 if(prime){ 22 for(i=0;i<3;i++){ 23 if(g_junioruser[i].isEqual(username, password)){ 24 g_juniorflag=true; 25 System.out.println("登陆成功!\n当前选择为初中出题"); 26 CurrentUser=g_junioruser[i]; 27 prime=false; 28 break; 29 } 30 } 31 } 32 if(prime){ 33 for(i=0;i<3;i++){ 34 if(g_highuser[i].isEqual(username, password)){ 35 g_highflag=true; 36 System.out.println("登陆成功!\n当前选择为初中出题"); 37 CurrentUser=g_highuser[i]; 38 prime=false; 39 break; 40 } 41 } 42 } 43 if(prime){ 44 System.out.println("请输入正确的用户名、密码!"); 45 CheckAccount(); 46 } 47 }
成员函数CheckAccount()的作用是核对用户信息,若信息匹配正确,则进行下一步,否则将重新输入用户名和密码。
(4) ChangeAccount()
1 /*----------------------------------------切换账户-------------------------------------*/ 2 public void ChangeAccount(){ 3 Scanner sc=new Scanner(System.in); 4 String info; 5 if((info=sc.nextLine()).length()!=0){ //判断输入是否为回车 6 if(info.equals("切换为小学")){ 7 g_primaryflag=true; 8 g_juniorflag=false; 9 g_highflag=false; 10 } 11 else if(info.equals("切换为初中")){ 12 g_primaryflag=false; 13 g_juniorflag=true; 14 g_highflag=false; 15 } 16 else if(info.equals("切换为高中")){ 17 g_primaryflag=false; 18 g_juniorflag=false; 19 g_highflag=true; 20 } 21 else{ 22 System.out.println("请输入小学、初中和高中三个选项中的一个"); 23 ChangeAccount(); 24 } 25 } 26 }
此成员函数的作用是切换账户类型,先判断输入是否为回车,若是回车则表示不用切换用户类型,否则根据用户输入进行切换类型,若输入错误则需要重新输入。
(5) UserAction()
1 /*--------------------------------------用户操作-------------------------------------*/ 2 public void UserAction(){ 3 CheckAccount(); 4 System.out.print("如果需要切换用户类型,请输入“切换为XX”(XX为小学、初中和高中三个选项中的一个,若不需要请按回车)"); 5 ChangeAccount(); 6 if(g_primaryflag){ 7 System.out.print("准备生成小学数学题目,请输入生成题目数量(题目数量的有效输入范围是“10-30”(含10,30,或-1退出登录))"); 8 } 9 if(g_juniorflag){ 10 System.out.print("准备生成初中数学题目,请输入生成题目数量(题目数量的有效输入范围是“10-30”(含10,30,或-1退出登录))"); 11 } 12 if(g_highflag){ 13 System.out.print("准备生成高中数学题目,请输入生成题目数量(题目数量的有效输入范围是“10-30”(含10,30,或-1退出登录))"); 14 } 15 }
将前面的3个成员函数整合为一个用户操作函数,使代码看起来更加的清晰明了。
(6)GetDate()
1 /*------------------------------------获取当前日期----------------------------------*/ 2 public String GetDate(){ 3 String date; 4 Calendar g=Calendar.getInstance(); 5 date=Integer.toString(g.get(Calendar.YEAR)); 6 date+="-"; 7 date+=Integer.toString(g.get(Calendar.MONTH)+1); 8 date+="-"; 9 date+=Integer.toString(g.get(Calendar.DATE)); 10 date+="-"; 11 date+=Integer.toString(g.get(Calendar.HOUR_OF_DAY)); 12 date+="-"; 13 date+=Integer.toString(g.get(Calendar.MINUTE)); 14 date+="-"; 15 date+=Integer.toString(g.get(Calendar.SECOND)); 16 return date; 17 }
获取当前日期函数,此函数过于复杂,其实可以利用Calendar类的格式将此代码简化,如下所示:
1 /*------------------------------------获取当前日期----------------------------------*/ 2 public String GetDate(){ 3 Calendar g=Calendar.getInstance(); 4 Date d=g.getTime(); 5 SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss"); 6 String formatDate=sdf.format(d); 7 return formatDate; 8 }
这样就精简了整段代码。
(7)PrimaryPaper()
1 /*--------------------------------------小学卷子----------------------------------*/ 2 public void PrimaryPaper(int number) throws IOException{ 3 Random r=new Random(); 4 String directory,filename,problem; 5 directory=username; 6 directory+="/小学试卷"; 7 filename=GetDate(); 8 filename+=".txt"; 9 File file=new File(directory+"/"+filename); 10 try { 11 // 创建新文件 12 file.createNewFile(); 13 FileOutputStream output=new FileOutputStream(file); 14 for(int i=0;i<number;i++){ 15 int count=0; //括号个数 16 int lastbrackets=0; // 记录上一个左括号位置 17 int operator=r.nextInt(4)+1; //选择操作符个数 18 problem=""; 19 if((r.nextInt(6)>3)&&(operator>1)){ 20 problem+="("; 21 count++; 22 } 23 problem+=Integer.toString(r.nextInt(100)+1); //产生1~100的随机数 24 for(int j=0;j<operator;j++){ 25 int choose1=r.nextInt(4)+1; //选择操符数类型1~4 26 problem+=Operator(choose1); 27 if((j<(operator/2))&&(r.nextInt(5)>1)&&(count==0)){ 28 problem+="("; 29 lastbrackets=j+1; 30 count++; 31 } 32 int choose2=r.nextInt(100)+1; //选择操作数1~100 33 problem+=Integer.toString(choose2); 34 if((count>0)&&(j+1-lastbrackets>0)&&(j!=operator-1)&&(r.nextInt(5)>2)){ 35 problem+=")"; 36 count--; 37 } 38 if((count>0)&&(j==operator-1)){ 39 problem+=")"; 40 } 41 } 42 problem+="="; 43 if(CurrentUser.ph.contains(problem)){ //判断是否有重复的题目 44 i--; 45 } 46 else{ 47 CurrentUser.ph.add(problem); 48 output.write((Integer.toString(i+1)+"、 "+problem).getBytes()); 49 output.write("\r\n\r\n".getBytes()); 50 } //输出换行符 51 } 52 output.close(); 53 }
这一段代码为出小学卷子的代码,考虑了多种情况:多括号叠加、同一张卷子上面不能有重复的题目等。但是没有考虑到括号有可能包含了整个式子的情况,如:(7+8+9)= 。
因此可以对代码进行以下修改:
1 for(int i=0;i<number;i++){ 2 int count=0; //括号个数 3 int lastbrackets=0; // 记录上一个左括号位置 4 int operator=r.nextInt(4)+1; //选择操作符个数 5 boolean firstflag=false; //***增加判断是否第一个操作数前有括号 6 problem=""; 7 if((r.nextInt(6)>3)&&(operator>1)){ 8 problem+="("; 9 count++; 10 firstflag=true; 11 } 12 problem+=Integer.toString(r.nextInt(100)+1); //产生1~100的随机数 13 for(int j=0;j<operator;j++){ 14 int choose1=r.nextInt(4)+1; //选择操符数类型1~4 15 problem+=Operator(choose1); 16 if(firstflag){ 17 if((j<(operator/2)-1)&&(r.nextInt(5)>1)){ 18 problem+="("; 19 lastbrackets=j+1; 20 count++; 21 } 22 } 23 else if((j<(operator/2))&&(r.nextInt(5)>1)){ 24 problem+="("; 25 lastbrackets=j+1; 26 count++; 27 } 28 int choose2=r.nextInt(100)+1; //选择操作数1~100 29 problem+=Integer.toString(choose2); 30 if((count>0)&&(j+1-lastbrackets>0)&&(j!=operator-1)){ 31 if(firstflag){ 32 problem+=")"; 33 count--; 34 } 35 else{ 36 if(r.nextInt(5)>2){ 37 problem+=")"; 38 count--; 39 } 40 } 41 }
增加一个布尔型变量来控制括号,改进代码之后查看卷子生成情况:
发现不会再出现这种问题。
(8)JuniorPaper()
生成初中卷子的函数,考虑到了单个操作数的多种情况,多括号情况,但是和函数PrimaryPaper()有相同的问题,如图:
因此直接放出修改后的主要部分代码:
1 try { 2 // 创建新文件 3 file.createNewFile(); 4 FileOutputStream output=new FileOutputStream(file); 5 for(int i=0;i<number;i++){ 6 int count=0; //括号个数 7 int lastbrackets=0; // 记录上一个左括号位置 8 boolean prime=false,firstflag=false; //当前题目中是否有平方/根号 ***增加判断是否第一个操作数前有括号 9 int operand=r.nextInt(5)+1; //选择操作数个数 10 problem=""; 11 if((r.nextInt(6)>3)&&operand>2){ //****改为操作数大于2才可以加括号 12 problem+="("; 13 count++; 14 firstflag=true; 15 } 16 if(operand==1){ //只有一个操作数 17 if(r.nextInt(4)>1){ 18 problem+="√"; 19 prime=true; 20 } 21 } 22 else if(r.nextInt(10)<2){ 23 problem+="√"; 24 prime=true; 25 } 26 problem+=Integer.toString(r.nextInt(100)+1); //产生1~100的随机数 27 if((operand==1)&&(prime!=true)){ 28 problem+="^2"; 29 } 30 else if(!prime){ 31 if(r.nextInt(8)<1){ 32 problem+="^2"; 33 prime=true; 34 } 35 } 36 for(int j=0;j<operand-1;j++){ 37 boolean flag=false; //表示当前操作数是否加上根号平方 38 int choose1=r.nextInt(4)+1; //选择操符数类型1~4 39 problem+=Operator(choose1); 40 41 if(firstflag){ 42 if((j<((operand-1)/2)-1)&&(r.nextInt(5)>1)){ 43 problem+="("; 44 lastbrackets=j+1; 45 count++; 46 } 47 }else if((j<((operand-1)/2))&&(r.nextInt(5)>1)&&(count<2)){ 48 problem+="("; 49 lastbrackets=j+1; 50 count++; 51 } 52 if(r.nextInt(7)<2){ 53 problem+="√"; 54 prime=true; 55 flag=true; 56 } 57 int choose2=r.nextInt(100)+1; //选择操作数1~100 58 problem+=Integer.toString(choose2); 59 if(!flag){ 60 if(r.nextInt(7)<2){ 61 problem+="^2"; 62 prime=true; 63 } 64 } 65 if(!prime&&j==operand-2){ 66 problem+="^2"; 67 } 68 if((count>0)&&(j==operand-2)){ 69 problem+=")"; 70 } 71 else if((count>0)&&(j+1-lastbrackets>0)&&(j!=operand-2)){ //***增加判断情况,不出现头尾括号的情况 72 if(firstflag){ 73 problem+=")"; 74 count--; 75 }else{ 76 if(r.nextInt(5)>1){ 77 problem+=")"; 78 count--; 79 } 80 } 81 } 82 83 } 84 if(CurrentUser.jh.contains(problem)){ //判断是否有重复的题目 85 i--; 86 } 87 else{ 88 problem+="="; 89 CurrentUser.jh.add(problem); 90 output.write((Integer.toString(i+1)+"、 "+problem).getBytes()); 91 output.write("\r\n\r\n".getBytes()); 92 } //输出换行符 93 } 94 output.close(); 95 }
查看修改代码后的卷子生成情况:
发现出现的问题已被解决。
(9) HighPaper()
生成初中卷子的函数,考虑到了单个操作数的多种情况,多括号情况,但是和函数PrimaryPaper()有相同的问题,并且出现了多平方问题,如图:
问题①:多平方问题
原因:判断时少加了一个else
解决方案:
修改代码如下:
问题②:可能出现括号将整个式子括住的情况
原因:没有对第一个操作数前的括号进行判断
解决方案:和PrimaryPaper()的解决方案相同
问题③:出题时竟然忘记在最后加上等号,小学卷子都加上了
原因:写代码的童鞋太过于粗心
解决方案:加上就好
总的来说,修改后的代码如下:
1 /*--------------------------------------高中卷子---------------------------------*/ 2 public void HighPaper(int number)throws IOException{ 3 Random r=new Random(); 4 String directory,filename,problem; 5 directory=username; 6 directory+="/高中试卷"; 7 filename=GetDate(); 8 filename+=".txt"; 9 File file=new File(directory+"/"+filename); 10 try { 11 // 创建新文件 12 file.createNewFile(); 13 FileOutputStream output=new FileOutputStream(file); 14 for(int i=0;i<number;i++){ 15 int count=0; //括号个数 16 int lastbrackets=0; // 记录上一个左括号位置 17 boolean bflag=false; //记录三角函数是否有括号 18 boolean prime1=false,prime2=false,firstflag=false; //当前题目中是否有平方/根号 、三角函数 ***增加判断是否第一个操作数前有括号 19 int operand=r.nextInt(5)+1; //选择操作数个数 20 problem=""; 21 if((r.nextInt(6)<1)&&operand>1){ 22 problem+="("; 23 count++; 24 firstflag=true; 25 } 26 if(operand==1){ //只有一个操作数 27 int choose3=r.nextInt(3)+5; //选择操符数类型5~7 28 if(r.nextInt(4)>1){ 29 problem+="("; 30 count++; 31 bflag=true; 32 } 33 problem+=Operator(choose3); 34 if(r.nextInt(4)>1){ 35 problem+="√"; 36 prime2=true; 37 } 38 } 39 else if(r.nextInt(7)<2){ 40 int choose3=r.nextInt(3)+5; //选择操符数类型5~7 41 if(r.nextInt(5)<1){ 42 problem+="("; 43 bflag=true; 44 count++; 45 } 46 problem+=Operator(choose3); 47 if(r.nextInt(4)>1){ 48 problem+="√"; 49 prime2=true; 50 } 51 } 52 problem+=Integer.toString(r.nextInt(100)+1); //产生1~100的随机数 53 if((operand==1)){ 54 if(bflag) 55 problem+=")"; 56 problem+="^2"; 57 } 58 else if(!prime2){ 59 if(bflag){ //记录三角函数是否有括号 60 problem+=")"; 61 count--; 62 bflag=false; 63 problem+="^2"; 64 } 65 else if(r.nextInt(8)<1){ 66 problem+="^2"; 67 prime2=true; 68 } 69 } 70 for(int j=0;j<operand-1;j++){ 71 boolean flag=false; //表示当前操作数是否加上根号平方 72 int choose1=r.nextInt(4)+1; //选择操符数类型1~4 73 problem+=Operator(choose1); 74 if(firstflag){ 75 if((j<((operand-1)/2)-1)&&(r.nextInt(5)>1)){ 76 problem+="("; 77 lastbrackets=j+1; 78 count++; 79 } 80 } 81 else if((j<((operand-1)/2))&&(r.nextInt(5)>1)&&(count==0)){ 82 problem+="("; 83 lastbrackets=j+1; 84 count++; 85 } 86 if(!prime1&&j==operand-2){ 87 int choose3=r.nextInt(3)+5; //选择操符数类型5~7 88 problem+=Operator(choose3); 89 } 90 else if(r.nextInt(5)<2){ 91 int choose3=r.nextInt(3)+5; //选择操符数类型5~7 92 if(r.nextInt(5)<1){ 93 problem+="("; 94 bflag=true; 95 count++; 96 } 97 problem+=Operator(choose3); 98 } 99 if(r.nextInt(7)<2){ 100 problem+="√"; 101 flag=true; 102 } 103 int choose2=r.nextInt(100)+1; //选择操作数1~100 104 problem+=Integer.toString(choose2); 105 if(bflag){ //记录三角函数是否有括号 106 problem+=")"; 107 bflag=false; 108 count--; 109 problem+="^2"; 110 } 111 if((count>0)&&(j+1-lastbrackets>0)&&(j!=operand-1)){ 112 if(firstflag){ 113 problem+=")"; 114 count--; 115 } 116 else{ 117 if(r.nextInt(5)>1){ 118 problem+=")"; 119 count--; 120 } 121 } 122 } 123 if((count>0)&&(j==operand-2)){ 124 problem+=")"; 125 } 126 } 127 if(CurrentUser.hh.contains(problem)){ //判断是否有重复的题目 128 i--; 129 } 130 else{ 131 problem+="="; 132 output.write((Integer.toString(i+1)+"、 "+problem).getBytes()); 133 output.write("\r\n\r\n".getBytes()); 134 } //输出换行符 135 } 136 output.close(); 137 }catch (IOException e) { 138 System.out.println("创建新文件时出现了错误。。。"); 139 e.printStackTrace(); 140 } 141 }
(10)PaperMaking()函数
1 /*-----------------------------------------出卷子------------------------------------*/ 2 public void PaperMaking()throws IOException{ 3 int number; 4 Scanner sc=new Scanner(System.in); 5 number=sc.nextInt(); 6 if(number==-1){ 7 UserAction(); 8 PaperMaking(); 9 } 10 else{ 11 if((number>=10)&&(number<=30)){ 12 if(g_primaryflag){ 13 PrimaryPaper(number); 14 } 15 if(g_juniorflag){ 16 JuniorPaper(number); 17 } 18 if(g_highflag){ 19 HighPaper(number); 20 } 21 System.out.println("出题成功!"); 22 } 23 else{ 24 System.out.println("请输入有效题目数量"); //*** 25 PaperMaking(); //***之前忘记循环重新输入 26 } 27 } 28 sc.close(); 29 } 30 }
此函数的作用是通过出题标志位判断当前为哪一个类型的题目出题,使用户输入出题数量,由于三个出题函数的模块化,此函数看起来非常的简洁清晰。但是最好在出题结束后提示用户卷子生成成功,不让整个程序结束得有些突兀。
(10)总结:
PryandSndpapers.java部分代码模块很清晰,但是漏洞较多,作者比较粗心,一些简单的问题都未能发现,完成的比较仓促,经过修改后明显改善了许多。
3、Main.java
1 public class Main { 2 3 public static void main(String[] args) throws IOException{ 4 PryandSndpapers psp=new PryandSndpapers(); 5 psp.InitAccount(); 6 psp.UserAction(); 7 psp.PaperMaking(); 8 } 9 }
三、整体总结
整体来看全部代码的模块性是非常清晰的,增强了其复用性,并且功能都全部实现。配以详细的注释代码的可读性很高,作者对于异常等的运用也都十分熟练。代码语句的书写规范,逻辑结构也很清晰。虽有些代码块仍可简化,但总体来说还是一份优秀的代码。