中小学数学卷子自动生成程序-个人项目互评
个人项目互评
代码作者:软件2101胡信航
评价者:软件2101马千凌
一、项目要求
实现中小学数学卷子自动生成程序:
-
命令行输入用户名和密码,两者之间用空格隔开,如果用户名和密码都正确,将根据账户类型(小学、初中和高中)准备出题。预设的有以下账户:
账户类型 账户 密码 小学 张三1 123 小学 张三2 123 小学 张三3 123 初中 李四1 123 初中 李四2 123 初中 李四3 123 高中 王五1 123 高中 王五2 123 高中 王五3 123 -
登录后输入范围“10-30”(含10,30,或-1退出登录)的,程序根据输入的题目数量生成符合小学、初中和高中难度的题目的卷子。难度要求如下:
小学 初中 高中 难度要求 +,-,*./ 平方,开根号 sin,cos,tan 备注 只能有+,-,*./和() 题目中至少有一个平方或开根号的运算符 题目中至少有一个sin,cos或tan的运算符 -
生成的题目将以“年-月-日-时-分-秒.txt”的形式保存,每个账号一个文件夹。每道题目有题号,每题之间空一行。
-
在登录状态下,如果用户需要切换类型选项,命令行输入“切换为XX”,XX为小学、初中和高中三个选项中的一个,输入项不符合要求时,程序控制台提示“请输入小学、初中和高中三个选项中的一个”;输入正确后,显示“”系统提示“准备生成XX数学题目,请输入生成题目数量”,用户输入所需出的卷子的题目数量,系统新设置的类型进行出题。
二 、项目结构
- 程序的入口为Main文件的main方法,先创建一个Run类的对象,再调用其初始化方法启动程序,main函数简洁易懂。
- Run类为Runnable接口的实现,主要包含一些用于处理逻辑跳转的函数,这些方法分为2层,第一层处理用户登录,第二层处理用户输入的指令,层次分明。
- Run类中应用到的Login类和PaperGeneration类均由相应接口实现而来,分别实现用户登陆、生成试卷的功能。
- 用户类User为抽象类,自带密码检查的方法,小、初、高老师均继承User类,在此基础上增加了获取其学校级别的方法。
运行流程图:

三、代码分析
-
Main类
public class Main { public static void main(String[] args) { Run run = new Run(); run.init(); } }
通过创建一个Run类的对象,再调用其初始化方法开始程序。
-
User抽象类
public abstract class User { private String userName; private String password; User(String userName, String password) { this.userName = userName; this.password = password; } public String getUserName() { return userName; } /** * 验证输入的账号密码是否匹配. */ public boolean check(String userName, String password) { return this.userName.equals(userName) && this.password.equals(password); } /** * 返回年级信息(小学、初中、高中). */ public abstract String getLevel(); }
User抽象类有三个继承:
-
public class PrimaryTeacher extends User { public PrimaryTeacher(String userName, String password) { super(userName, password); } @Override public String getLevel() { return "小学"; } }
-
public class MiddleTeacher extends User { public MiddleTeacher(String userName, String password) { super(userName, password); } @Override public String getLevel() { return "初中"; } }
-
public class HighTeacher extends User { public HighTeacher(String userName, String password) { super(userName, password); } @Override public String getLevel() { return "高中"; } }
对于不同的继承,在调用getLevel()函数时返回不同的结果,以此表现不同的用户类型。
-
-
Runnable接口
public interface Runnable { /** * 初始化和运行函数,第一层处理登录,第二层处理输入. */ void init(); /** * 判断输入是否是数字. */ boolean isNumber(String num); /** * 处理输入的字符,输入正确,实现当前试卷类型的切换,输入错误,给出提示信息. */ int inputString(String s); /** * 处理输入的字符,输入正确,生成指定数量的题目,输入错误,给出提示信息. */ int inputNumber(String num); /** * 处理输入命令,先判断是否为数字,再调用函数判断格式. */ int inputCommand(String command); /** * 将题目类型转换为数字. */ int inputLevel(String level); }
运行类Run的接口,比较重要的几个方法如下:
-
@Override public void init() { User user = login.login(); state = user.getLevel(); System.out.println("当前选择为" + state + "出题"); System.out.println("准备生成" + state + "数学题目,请输入生成题目数量(输入-1将退出当前用户,重新登录):"); int command; while (true) { command = inputCommand(in.nextLine()); if (command == 1) { continue; } if (command == -1) { init(); } else { paperGeneration.paperGeneration(command, user.getUserName(), inputLevel(state)); } } }
负责登录信息的显示、处理输入命令解析后的结果以及试卷的生成。
-
@Override public int inputCommand(String command) { String[] param = command.split(" "); int l = param.length; if (l != 1) { System.out.println("请输入有效格式"); return 1; } if (!isNumber(command)) { //如果输入的不是数字,返回0 if (!command.startsWith("切换为")) { System.out.println("请输入正确格式"); } else if (inputString(command) == 1) { //如果成功切换 System.out.println("准备生成" + state + "数学题目,请输入生成题目数量"); } return 1; } else { //如果输入的是数字,返回输入值 int n = inputNumber(command); if (n == 0) { return 1; } return n; } }
将用户输入的命令解析为返回值提供给init()。
-
@Override public int inputString(String s) { switch (s) { case "切换为初中" -> { if (state.equals("初中")) { System.out.println("类型已经为初中,请勿重复设置"); } else { state = "初中"; return 1; } } case "切换为小学" -> { if (state.equals("小学")) { System.out.println("类型已经为小学,请勿重复设置"); } else { state = "小学"; return 1; } } case "切换为高中" -> { if (state.equals("高中")) { System.out.println("类型已经为高中,请勿重复设置"); } else { state = "高中"; return 1; } } default -> System.out.println("请输入小学、初中和高中三个选项中的一个"); } return 0; }
处理切换出题难度的方法。
-
-
Loginable接口
public interface Loginable { /** * 用户文件读取,用于读取文件并初始化用户数组. */ void readFile(); /** * check函数用于遍历用户数组,账号密码匹配返回在数组的下标,否则返回-1. */ int check(String userName, String password); /** * login函数用于命令行实现用户登录. */ User login(); }
Login类的接口,主要实现登录功能。
-
PaperGenerationable接口
public interface PaperGenerationable { String[] symbols = { "+", "-", "*", "/" }; String[] power = { "²", "√(", ")" }; String[] trigonometric = { "sin", "cos", "tan", "°" }; /** * 按格式返回当前时间. */ String time(); /** * 用于改变数字的值,使数字带上平方或者根号,满足初中难度. */ String changeMiddle(int input); /** * 用于改变数字的值,使数字带上三角函数,满足高中难度. */ String changeHigh(int input); /** * 根据难度处理单个数字. * * @param level 试卷类型 * @param n 要处理的数字 * @param j 现在的数字位置 * @param r 一个问题中总共有多少数字 * @return String 处理完的数字 */ String getChange(int level, int n, int j, int r); /** * 为单个数字加上符号或者括号. * * @param j 处理1个问题中的第几个数字 * @param num 括号位置 * @param p 经过难度处理的数字 * @param question 问题 * @param s 符号在数组中的位置 * @param r 题目有多少个数字 * @return String 对一个数字处理后的问题 */ String getQuestion(int j, int num, String p, String question, int s, int r); /** * 按类型生成单个题目. */ String generateQuestion(int level); /** * 按照题目的数量,当前用户,要生成的类型生成对应试卷. */ void paperGeneration(int number, String userName, int level); /** * 读取指定用户已经出的题,放入paperSet中. */ void readFile(String userName); /** * 将一份试卷写入文件中. */ void writeFile(HashSet<String> paper, String userName, String filename); /** * 对指定题目进行查重,没有重复返回真,否则返回假. */ boolean duplicateCheck(String question, String userName); }
此为生成试卷的类PaperGeneration的接口,定义了几个符号集和一些接下来要用到的函数。比较重要的几个函数的实现如下:
-
@Override public String getChange(int level, int n, int j, int r) { String p; if (level == 0) { //小学 p = Integer.toString(n); } else { //初中 flag = 0; if (level == 1) { p = changeMiddle(n); if (j == r && flag == 0) { p = power[1] + n + power[2]; //如果一道题没有一个平方或者根号,使最后一个数加上根号 } } else { p = changeHigh(n); if (j == r && flag == 0) { p = trigonometric[0] + n + trigonometric[3]; //如果一道题没有一个三角函数,使最后一个数变为sin } } } return p; }
检查题目是否含有特殊运算(在初高中难度下),如果没有则给最后的操作数加上特殊运算。
-
@Override public String getQuestion(int j, int num, String p, String question, int s, int r) { if (j == num && r != j) { //添加左括号,前提不是题目中最后一个数字 if (j == 0) { question = "(" + p + " "; } else { question += symbols[s] + " (" + p + " "; } } else if (j == num + 1 && j != 0) { //添加右括号 question += symbols[s] + " " + p + ") "; } else { //不添加括号 if (j == 0) { question = p + " "; } else { question += symbols[s] + " " + p + " "; } } return question; }
为文件中的某个操作数加上符号或者括号。
-
@Override public String generateQuestion(int level) { String question = ""; int r = random.nextInt(5); //r表示这个题目有多少个数字 if (level == 0 && r == 0) { r++; } int num = -1; //在哪个地方插入括号 if (r >= 2) { num = random.nextInt(4); } for (int j = 0; j <= r; j++) { int s = random.nextInt(4); //生成符号+-*/中的一个 String p; //p表示1个数字的变化 int n = random.nextInt(100) + 1; p = getChange(level, n, j, r); if (j == num && s == 3) { //如果括号在除法下,去掉括号,防止0的出现 num = -1; } question = getQuestion(j, num, p, question, s, r); } question += "="; return question; }
生成单道题目。
-
@Override public void paperGeneration(int number, String userName, int level) { HashSet<String> paper = new HashSet<>(); for (int i = 0; i < number; i++) { String question = generateQuestion(level); //生成1个题目 if (duplicateCheck(question, userName)) { //查重 paper.add(question); //添加到HashSet中 } else { i--; //如果重复,重新生成题目 } } writeFile(paper, userName, time()); //写入文件 }
按照题目的类型、当前用户、题目数量生成符合要求的文件。
生成的题目经过查重后加入历史题目集。
-
@Override public boolean duplicateCheck(String question, String userName) { readFile(userName); //读取已经出过的题,添加到HashSet中 for (String s : paperSet) { //遍历集合 if (question.equals(s)) { //如果重复,返回false return false; } } return true; }
将生成的单道题目和用户历史题目比较,相同则重复,返回真。
-
四、程序测试
1. 登陆:

可以发现如果输入错误的格式、未输入密码、密码错误均会有相应报错,报错后可以继续进行输入。
输入正确的账号、密码,程序会有相应提示,且会说明当前的出题难度。

退出当前账号并登录其他账号,退出过程没有问题,且重新登录后均有正确的提示。
经过测试,登录部分可以正常运行且没有bug。
2. 切换命令测试

输入错误指令、一行输入多个指令均会有相应提示,输入正确指令可以正常切换,在已经是某一难度的情况下再次切换该难度同样会有提示。
经过各种测试,切换难度部分逻辑完备。
3. 题目生成测试

输入超过范围的数量、同一行输入多个数字会有错误提示。
输入符合要求的值则会在当前用户的文件夹内生成所需的文件,文件名为当前时间,符合要求。
生成完文件后程序提示继续输入命令,可以退出或者继续生成题目。

生成的题目符合当前难度的要求,数量也没有问题。

切换难度,生成的题目同样符合要求。


切换用户,文件生成位置、题目难度发生相应改变,验证后均准确无误。
4. 查重测试
查重有关代码如下:
@Override
public void paperGeneration(int number, String userName, int level) {
HashSet<String> paper = new HashSet<>();
for (int i = 0; i < number; i++) {
String question = generateQuestion(level); //生成1个题目
if (i == 0) { //测试查重
question = "91 - 76 =";
i++;
}
if (duplicateCheck(question, userName)) { //查重
paper.add(question); //添加到HashSet中
} else {
i--; //如果重复,重新生成题目
}
}
writeFile(paper, userName, time()); //写入文件
}
检查上传效果的方法如下:修改paperGeneration函数,添加一条if语句测试查重功能。将第一个问题改为“91 - 76 =”,这道题在张三1出过的试卷中已经出现,为避免进入死循环,将i++,最后会生成number-1道题目,只需检查试卷中有无指定题目“91 - 76 =”即可。


查看文本文件,发现并没有出现91-76这道题,查重有效。


将91-76改为91+76,这道题没有在之前的试卷中出现,重新生成,发现有这道题目,证明查重功能有效。
五、优缺点分析
优点
- 代码在功能方面没有问题,对各种错误输入均有处理。
- 代码符合Google语言规范,注释齐全。
- 文件保存在相对路径output/paper/name下,文件名和需求相同。
- 具体类均有相应接口类或抽象类(不考虑Main),根据功能不同分为几个类,较为直观。
- 方法行数均未超过40行,阅读难度不高。
- 咋子高中难度下对操作数的值进行了限制,防止出现tan(90°)等情况。
可改进的点
-
因为随机性的原因乘、除号常常在同一题中连续出现,导致题目计算过于困难。
-
涉及到三角函数时会生成难以手算的式子。
-
文件结构可以进行些许优化,利用package可以让文件结构更清晰。
-
方法命名、传入参数的命名不是很直观,可以改进。
六、评分
该个人项目两个方法将单个字母作为比较重要的变量,扣10分。
本文作者:hnu-mql's
本文链接:https://www.cnblogs.com/hnu-mql/p/17715438.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步