功能:
1、命令行输入用户名和密码,两者之间用空格隔开(程序预设小学、初中和高中各三个账号,具体见附表),如果用户名和密码都正确,将根据账户类型显示“当前选择为XX出题”,XX为小学、初中和高中三个选项中的一个。否则提示“请输入正确的用户名、密码”,重新输入用户名、密码;
2、登录后,系统提示“准备生成XX数学题目,请输入生成题目数量(输入-1将退出当前用户,重新登录):”,XX为小学、初中和高中三个选项中的一个,用户输入所需出的卷子的题目数量,系统默认将根据账号类型进行出题。每道题目的操作数在1-5个之间,操作数取值范围为1-100;
3、题目数量的有效输入范围是“10-30”(含10,30,或-1退出登录),程序根据输入的题目数量生成符合小学、初中和高中难度的题目的卷子(具体要求见附表)。同一个老师的卷子中的题目不能与以前的已生成的卷子中的题目重复(以指定文件夹下存在的文件为准);
4、在登录状态下,如果用户需要切换类型选项,命令行输入“切换为XX”,XX为小学、初中和高中三个选项中的一个,输入项不符合要求时,程序控制台提示“请输入小学、初中和高中三个选项中的一个”;输入正确后,显示“”系统提示“准备生成XX数学题目,请输入生成题目数量”,用户输入所需出的卷子的题目数量,系统新设置的类型进行出题;
5、生成的题目将以“年-月-日-时-分-秒.txt”的形式保存,每个账号一个文件夹。每道题目有题号,每题之间空一行;
队友的代码中有三个类,User类,Producer类,Server类。主函数在Server类中,通过调用在类中的函数实现各个功能模块。
User类:User() 传入的用户信息的初始化
Server类:userInit() 已有用户的初始化
login() 用户登录
startFunction() 获取题目类型、数量
Producer类:txtProduce() 生成试卷
getPast() 获取以前的题目放入哈希表
stringProduce() 生成算式
功能实现的流程如下:
public static void main(String[] args) { userInit(); login(); }
public static void userInit() { users = new ArrayList<>(); users.add(new User("小学","张三1","123")); users.add(new User("小学","张三2","123")); users.add(new User("小学","张三3","123")); users.add(new User("初中","李四1","123")); users.add(new User("初中","李四2","123")); users.add(new User("初中","李四3","123")); users.add(new User("高中","王五1","123")); users.add(new User("高中","王五2","123")); users.add(new User("高中","王五3","123")); }
匹配成功则进入下一步,匹配不成功则会一直在循环中不断获取输入的用户名和密码,直到匹配成功,即进入登录状态,从而执行下一步;
public static void login() { while (true) { //输入 currentUser.type = ""; //type是一个输入是否成功的标志,必须初始化 String tmpUsername = "", tmpPassword = ""; System.out.println("请输入用户名和密码"); Scanner in = new Scanner(System.in); String line = in.nextLine(); int pos = line.indexOf(" "); if (pos >= 0) { tmpUsername = line.substring(0, pos); tmpPassword = line.substring(pos + 1); } //判断是否正确 for (User user : users) { //查找有无匹配 if (user.username.equals(tmpUsername) && user.password.equals(tmpPassword)) { currentUser.type = user.type; currentUser.password = user.password; currentUser.username = user.username; } } //错误处理和跳出循环 if (currentUser.type == "") { System.out.println("请输入正确的用户名、密码"); } else { System.out.println("当前选择为"+currentUser.type+"出题"); startFunction(); break; } } }
这里是根据用户的几种输入分成了四个分支,对每一个分支进行具体的处理;根据输入:
-1:退出登录,进行重新登录,调用login()函数,即回到步骤③中;
切换为...:格式正确,则类型切换,设置标志位,重新进入循环,继续获取输入;格式不正确,打印提示信息后,设置标志位,重新进入循环,继续获取输入;
整数:数量正确:生成题目(进入下一个步骤),待完成返回后,再次进入循环(即再次从步骤④开始),进行新一轮的用户要求的获取;数量不正确:打印信息后,重新进入循环,获取用户的题目要求;
public static void startFunction() { int num; //生成题目数量 boolean flag = true;//标志是否需要输出“准备生成...” String type = currentUser.type; //因为可能修改,另存一个type while(true) { if (flag) { System.out.println("准备生成" + type + "数学题目,请输入生成题目数量" + "(输入-1将退出当前用户,重新登录):"); } flag = true; Scanner in = new Scanner(System.in); String command = in.nextLine(); //重新登陆 if (command.equals("-1")) { login(); return; } else if (command.contains("切换为")) { //切换 if (command.equals("切换为小学")) { type = "小学"; } else if (command.equals("切换为初中")) { type = "初中"; } else if (command.equals("切换为高中")) { type = "高中"; } else { System.out.println("请输入小学、初中和高中三个选项中的一个"); flag = false; } } //生成卷子 else if (command.matches("\\d+")) { num = Integer.valueOf(command); if (num >= 10 && num <= 30) { String name = Producer.txtProduce(type, num); System.out.println("生成完毕,试卷已经保存在相应文件夹中,文件名:" + name); } else { System.out.println("请输入10~30之间的数字"); flag = false; } } else { System.out.println("没用该指令,请重新输入"); } } }
这个函数中首先是调用Producer.getpast(),获取之前的题目,将它们加入Producer中的哈希表,用于之后的查重;之后,建立路径,生成文件名,创建文件,尝试向创建的文件放入从Producer.stringProduce()中获取生成的题目,并将题目放入哈希表中,便于查重;
public static String txtProduce(String _type, int num) { type = _type; //绝对路径 dir = "C:\\Users\\渔羊\\Desktop\\" + currentUser.username + "\\"; //相对路径 //dir = "pages/" + currentUser.username + "/"; getPast(); //获得之前的题目放到哈希表中 Calendar time = Calendar.getInstance(); int year = time.get(Calendar.YEAR); int month = time.get(Calendar.MONTH) + 1; int date = time.get(Calendar.DATE); int hour = time.get(Calendar.HOUR); int minute = time.get(Calendar.MINUTE); int second = time.get(Calendar.SECOND); //生成文件路径 String name = String.valueOf(year)+"-"+String.valueOf(month)+"-"+String.valueOf(date)+ "-"+String.valueOf(hour)+"-"+String.valueOf(minute)+"-"+String.valueOf(second)+".txt"; String path = dir + name; try { FileWriter filewriter = new FileWriter(path, true); //追加 for (int i = 0; i < num; i++) { String content = stringProduce(); String line = String.valueOf(i + 1) + ") " + content; filewriter.write(line + "\n" + "\n"); filewriter.flush(); //System.out.println(line); //新生成的题目加入哈希表 pastProblem.add(content); } filewriter.close(); } catch (IOException e) { e.printStackTrace(); } return name; } //获得以前的题目放入哈希表 static void getPast() { File files = new File(dir); //获取目录下的file if (!files.exists()) { files.mkdir(); } File[] fileList = files.listFiles(); //遍历文件list,放入哈希表 for (File f : fileList) { if (!f.isDirectory()) { //判断:非目录 try { String line; //存储行字符串 BufferedReader br = new BufferedReader(new FileReader(f)); while((line = br.readLine()) != null) { String content; //提取题目 int pos = line.indexOf(" "); content = line.substring(pos + 1); pastProblem.add(content); } br.close(); } catch (Exception e) { e.printStackTrace(); } } } }
括号的生成:随机加入左括号,设置标志位,在同一个操作数前后不同时出现左右括号,实时记录左括号位置,在标志位许可,且左括号数量大于零时,随机加入右括号,在表达式的最后,将剩余为加入的右括号全部加入;
初中与高中的高级运算符的加入:才前numCount-1个操作数中随机加入高级操作符,加入时要改变其标志位,如果在之前始终未加入过(通过相关标志位判断),那么就在最后一个操作数位置加入一个随机的高级运算符,以确保满足需求中的至少有一个高级运算符;
static String stringProduce() { String problem = ""; do { problem = ""; String[] operator = {"+", "-", "*", "/", "^2", "√", "sin", "cos", "tan"}; Random random = new Random(); boolean flagOp = false; //记录是否有必须的运算符 int num = 0; if (type == "小学") { num = random.nextInt(4) + 2; //操作数个数:区间[2, 6) } else { num = random.nextInt(5) + 1; //操作数个数:区间[1, 6) } int numLeft = 0; //记录无匹配的左括号数目 //前面num-1个操作数 for (int i = 0; i < num - 1; i++) { boolean flagLeft = false; //左括号, 1/3概率加入 if (i != 0) { if (random.nextInt(3) == 1) { problem += "("; numLeft++; flagLeft = true; } } //操作数单元(包含高阶运算符的操作数) String cell; String op = ""; String strNum = String.valueOf(random.nextInt(100) + 1); //操作数 if (type == "初中") { if (random.nextInt(2) == 1) { // 1/2概率加入高阶运算符 op = operator[random.nextInt(2) + 4]; flagOp = true; } } else if (type == "高中") { if (random.nextInt(3) == 1) { // 1/3概率 op = operator[random.nextInt(2) + 4]; } else if (random.nextInt(2) == 1) { // 1/3概率 op = operator[random.nextInt(3) + 6]; flagOp = true; } } if (op == "^2") { cell = strNum + op; }else { cell = op + strNum; } problem += cell; //右括号,注意左右括号不能都加 if (numLeft > 0 && flagLeft == false) { if (random.nextInt(2) == 1) { problem += ")"; numLeft--; } } //运算符 problem += operator[random.nextInt(4)]; } //最后一个cell //操作数单元(包含高阶运算符的操作数) String cell; String op = ""; String strNum = String.valueOf(random.nextInt(100) + 1); //操作数 if (type == "初中") { if (flagOp == false) { op = operator[random.nextInt(2) + 4]; } } else if (type == "高中") { if (flagOp == false) { op = operator[random.nextInt(3) + 6]; } } if (op == "^2") { cell = strNum + op; } else { cell = op + strNum; } problem += cell; //补完右括号 for (;numLeft > 0; numLeft--) { problem += ")"; } } while (pastProblem.contains(problem)); return problem; }
请输入用户名和密码 张三1 123 当前选择为小学出题 准备生成小学数学题目,请输入生成题目数量(输入-1将退出当前用户,重新登录): 切换为初中 准备生成初中数学题目,请输入生成题目数量(输入-1将退出当前用户,重新登录): 30 生成完毕,试卷已经保存在相应文件夹中,文件名:2022-9-13-10-32-37.txt
登录并直接获取正确数量的题目
准备生成初中数学题目,请输入生成题目数量(输入-1将退出当前用户,重新登录): 切换 没用该指令,请重新输入 准备生成初中数学题目,请输入生成题目数量(输入-1将退出当前用户,重新登录): 切换为高 请输入小学、初中和高中三个选项中的一个 切换为高中 准备生成高中数学题目,请输入生成题目数量(输入-1将退出当前用户,重新登录): 40 请输入10~30之间的数字 30 生成完毕,试卷已经保存在相应文件夹中,文件名:2022-9-13-10-33-4.txt
在用户进行类型切换时,能够正确处理错误的输入;
准备生成小学数学题目,请输入生成题目数量(输入-1将退出当前用户,重新登录): -1 请输入用户名和密码
输入“-1”时,能够退出登录
在正确的路径上创建文件,文件名符合要求;
生成的题目符合小初高的不同要求
为了实现查重功能的验证,利用我在自己的代码中验证查重功能的方法,在代码中加入以下测试代码:
boolean testFlag = false; do { if(testFlag) { problem = ""; ········
设置标志位,在尝试加入已有题目后,标志位设置为true,开始运行原来的题目生成代码
······ } else { problem = "11+35*64-6-38"; testFlag = true; }
从之前生成过并加入到文件的题目中选取一道,尝试加入正在生成的试卷,若无法加入,则说明查重功能实现;这里是用了张三1文件夹中保存的一道小学题,运行结果:
可以看到,之后生成的题目中没有这道题(因为测试代码在一开始就尝试加入旧题目,所以第一题不是旧题目,就说明查重功能实现);