题目
用户:
小学、初中和高中数学老师。
功能:
- 命令行输入用户名和密码,两者之间用空格隔开(程序预设小学、初中和高中各三个账号,具体见附表),如果用户名和密码都正确,将根据账户类型显示“当前选择为XX出题”,XX为小学、初中和高中三个选项中的一个。否则提示“请输入正确的用户名、密码”,重新输入用户名、密码;
- 登录后,系统提示“准备生成XX数学题目,请输入生成题目数量(输入-1将退出当前用户,重新登录):”,XX为小学、初中和高中三个选项中的一个,用户输入所需出的卷子的题目数量,系统默认将根据账号类型进行出题。每道题目的操作数在1-5个之间,操作数取值范围为1-100;
- 题目数量的有效输入范围是“10-30”(含10,30,或-1退出登录),程序根据输入的题目数量生成符合小学、初中和高中难度的题目的卷子(具体要求见附表)。同一个老师的卷子中的题目不能与以前的已生成的卷子中的题目重复(以指定文件夹下存在的文件为准,见5);
- 在登录状态下,如果用户需要切换类型选项,命令行输入“切换为XX”,XX为小学、初中和高中三个选项中的一个,输入项不符合要求时,程序控制台提示“请输入小学、初中和高中三个选项中的一个”;输入正确后,系统提示“准备生成XX数学题目,请输入生成题目数量”,用户输入所需出的卷子的题目数量,系统新设置的类型进行出题;
- 生成的题目将以“年-月-日-时-分-秒.txt”的形式保存,每个账号一个文件夹。每道题目有题号,每题之间空一行;
具体代码分析如下
用户类的设计
- 设计者将用户类设计为:账户名称 (name)、账户密码 (password)、账户类型 (grade),并构建了相对应的 get/set 函数。
- 值得注意的是,此时的账户类型(即 grade)是通过一个整型变量表示的,而不是表中默认的"小学""初中""高中"这种字符串类型,如此做的目的是在后续代码编写中方便对类型进行判断,这里感到困惑没有关系,我们接下来分析代码主体时便可体会到这样设计的方便之处~~~
public class User {
private String name;
private String password;
private int grade;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public int getGrade() {
return grade;
}
public void setGrade(int grade) {
this.grade = grade;
}
}
函数主体设计
因为设计者在函数主体部分设计较为庞大,因此我们将其代码分成几部分来进行详细分析:
- main 函数模块
- 此模块作为试卷出题系统的核心部分,调用了设计的各个方法,不过这些方法我们还没有分析到,不过我先来简单介绍一下各个方法的作用,相信这样可以让大家对接下来的分析有一个大体的了解:
- userInitialize():这个方法用来初始化用户列表,其返回值是一个 List 类型,通过调用此方法,可以得到已知的全部合法用户,可以用来在后续登录过程与用户输入的账号名及密码进行对比,判断是否为合法用户。
- login():用作登录操作,需要将一个 List 类的列表传入作为参数,会根据传入的列表判断当前用户是否为合法用户,进而判断是否可以登录。
- getPaper():出卷函数,其需要传入三个参数,包括用户的账号名,状态,出题数,最终会生成一个以账号名为名称的文件夹,其中包括以"年-月-日-时-分-秒.txt"为名称的试卷。
- 此模块通过一个 while 循环和 switch 模块来实现账户类型的改变以及出试卷的操作,不过这个 while 循环没有包括退出操作(emmm 虽然题目也没有要求,好吧!小问题啦),感觉加上一个 exit(0) 情况会更完整一些。
public static String dir="C:\\Users\\DELL\\Desktop\\JAVA\\Homework\\";//主文件夹位置
public static void main(String[] args) {
List<User> users=userInitialize();
User iuser=login(users);//记录当前用户
int state=iuser.getGrade();//记录当前年级状态
while(true){
int input=getInput(state);
switch (input){
case -1:iuser=login(users);//输入-1重新登录
state=iuser.getGrade();
break;
case 1:state=1;//改变年级状态
break;
case 2:state=2;//改变年级状态
break;
case 3:state=3;//改变年级状态
break;
default:getPaper(iuser.getName(),state,input);//获得试卷
}
}
}
- userInitialize() 模块
- 此块代码用来作为用户列表初始化操作,用到了 List 这种集合来存储用户信息。
- 通过FileReader读取指定的文件路径的内容并交给 BufferedReader ,在读取过程中,使用字符串分割操作,可以得到账户名、密码以及账户类型。其中账户类型由于从文件中读出的是"小学""初中""高中"这些字符串,因此要将其转换为整形 1,2,3 来存入 User 类。
- 最后一定要记得关闭 BufferedReader 呀!
static List<User> userInitialize(){
List<User> users=new ArrayList<User>();
File infile = new File(dir+"user.txt");//用户信息文件
try {
BufferedReader bf = new BufferedReader(new FileReader(infile));//读入文件
String temp;
while((temp=bf.readLine())!=null) {
String[] stringSplit=temp.split("\\s+");//按空格切分
User iuser=new User();//
iuser.setName(stringSplit[0]);//设置姓名
iuser.setPassword(stringSplit[1]);//设置密码
if(stringSplit[2].equals("小学")){//设置年级
iuser.setGrade(1);
}
if(stringSplit[2].equals("初中")){
iuser.setGrade(2);
}
if(stringSplit[2].equals("高中")){
iuser.setGrade(3);
}
users.add(iuser);//添加至用户链表
}
bf.close();
}catch(Exception e){
e.printStackTrace();
return null;
}
return users;//返回全部用户
}
- login() 模块
- login() 模块为登录模块,其也要用到上面提到的 userInitialize() 方法读出的 List
列表,通过将输入的账号名及密码在列表中进行匹配,即可判断用户输入的是否为合法用户。 - 具体操作代码注释已经很详细啦
static User login(List<User> users){
System.out.println("请输入用户名和密码:");
Scanner in=new Scanner(System.in);
boolean flag=false;//标记位用来标记是否登录
User iuser=new User();//表示登录用户
while(!flag) {
String str[] = in.nextLine().split("\\s+");
if(str.length<2){//输入少于两个字段
System.out.println("请输入正确的用户名、密码:");
continue;
}
String name = str[0];//记录姓名
String password = str[1];//记录密码
for(User user:users){
if(user.getName().equals(name)&&user.getPassword().equals(password)){//存在用户满足该用户名和密码
flag=true;
int grade=user.getGrade();
iuser.setName(user.getName());//设置用户名
iuser.setPassword(user.getPassword());//设置密码
iuser.setGrade(user.getGrade());//设置年级
System.out.print("当前选择为");//对应数字输出相应的年级
if (grade==1){
System.out.print("小学");
}
if (grade==2){
System.out.print("初中");
}
if (grade==3){
System.out.print("高中");
}
System.out.println("出题");
break;//登录成功跳出循环
}
}
if(!flag){
System.out.println("请输入正确的用户名、密码:");//如果没有满足该用户的账户,登录失败,重新输入
}
}
return iuser;//返回登录的账户信息
}
- getInput() 模块
- 该模块实现了账户类型的转换,同时也实现了题目数量输入的过程。
- 当传入参数为 1,2,3 时,对应为 "小学""初中""高中" 三种账户类型;当输入为"切换为"时,实现了账户类型的切换;当输入为 "-1" 时,用户退出登录;当输入为 [10, 30] 时,则说明时题目的数量。
static int getInput(int state){
System.out.print("准备生成");//对应的年级输出相应的提示语
if (state==1){
System.out.print("小学");
}
if (state==2){
System.out.print("初中");
}
if (state==3){
System.out.print("高中");
}
System.out.println("数学题目,请输入生成题目数量(输入-1将退出当前用户,重新登录):");
int get=0;
Scanner in=new Scanner(System.in);
String input=in.nextLine();//接收输入
while (true){
if(input.contains("切换为")){//如果满足字符串“切换为”则进行判断
while (true){
if(input.equals("切换为小学")){//满足条件跳出循环
get=1;
break;
}
if(input.equals("切换为初中")){
get=2;
break;
}
if(input.equals("切换为高中")){
get=3;
break;
}
System.out.println("请输入小学、初中和高中三个选项中的一个");//不满足条件继续重新输入
input=in.nextLine();
}
}
if(input.equals("-1")){//输入为1登出原来的用户
get=-1;
}
if (input.matches("\\d+")){//满足输入的是数字
int num=Integer.valueOf(input);
if (num>=10&&num<30){//在10到30之间满足条件
get=num;
}else{
System.out.println("输入范围错误,请重新输入");//不满足条件重新输入
input=in.nextLine();
continue;
}
}
if (get!=0){//输入有效则跳出循环返回结果
break;
}else{
System.out.println("输入错误,请重新输入");
}
}
return get;
}
- getProblem() 模块
- 由于出题模块太过冗长,因此我们只列举了出高中题的情况。
- 为了实现随机题目的生成,设计者使用了 Random 类产生随机数种子,接着通过随机数种子产生三角函数、平方、根号、普通符号以及括号。
- 如果题目长度过长的话便将题目舍弃,重新出题。
static String getProblem(int state){
String[] symbol=new String[]{"+","-","*","/","^2","√","sin","cos","tan"};//设置字符串数组保存符号
Random r = new Random();//产生随机数种子
String problem=new String();//创建字符串来保存结果
if(state==3){
while(true){
problem="";
int length=0;
int sym=1;
int sym2=r.nextInt(3)+1;//初始化三角函数个数大于1的整数
int bracket=r.nextInt(3);
int left=0;
int gap=0;
while (true){
if(r.nextInt(2)==0&&bracket>0){
problem+="(";
bracket--;
left++;
gap=0;
}
gap++;
int num=r.nextInt(100)+1;
if(r.nextInt(3)==0){//1/3的概率加入平方根号
if(r.nextInt(2)==0){
problem+=(String.valueOf(num)+symbol[4]);
}else{
problem+=(symbol[5]+String.valueOf(num));
}
sym--;
}else {//1/2的概率加入三角函数
if(r.nextInt(2)==0){
int symloc=r.nextInt(3)+6;
problem+=(symbol[symloc]+String.valueOf(num));
sym2--;
}else {//1/2的概率加入普通符号
problem+=String.valueOf(num);
}
}
if(r.nextInt(2)==0&&left>0&&gap>=2){
problem+=")";
left--;
}
if(length>=2&&sym<=0&&sym2<=0&&bracket<=0&&left<=0){
break;
}
problem+=symbol[r.nextInt(4)];
length++;
}
if(length<5&&gap<5){
//题目符合要求
break;
}
}
}
problem+="=";
return problem;
}
- getPast() 模块
- 该模块用作获取该老师曾经出过的所有题目,其解决思想是:遍历指定账号名的文件夹,读取该文件夹中的全部文件,并且将其中全部题目存储到一个 HashSet 中,由于 HashSet 有着不重复的特点,因此其中的题目必然只能出现过一次。
static HashSet<String> getPast(String name){
String path = dir+name; //要遍历的路径
File file = new File(path); //获取其file对象
if(!file.exists()){//如果没有该文件夹进行创建
file.mkdir();
}
File[] fs = file.listFiles(); //遍历path下的文件和目录,放在File数组中
HashSet<String> pastProblem= new HashSet<String>();
for(File f:fs) { //遍历File[]数组
if (!f.isDirectory()) //若非目录(即文件),则读入
{
try {
BufferedReader br = new BufferedReader(new FileReader(f));
String line;
while ((line = br.readLine()) != null) {
// 一次读入一行数据并加入到结果的hashset中
pastProblem.add(line);
}
br.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
return pastProblem;
}
- getPaper() 模块
- 该模块用来输出试卷,首先调用了上文提到的 getPast() 方法,用来获得该老师出过的题目,防止出现题目出重的情况。
- 接着调用 Calendar 类,得到当前的年,月,日,时,分,秒,目的是得到输出试卷的试卷名称。
- 接着使用 FileWriter 向文件中写入题目,调用的是前文提过的 getProblem() 方法,注意不要忘记加上题号以及隔一行写一道题目!
static void getPaper(String name,int state,int num){
HashSet<String> past=getPast(name);//获得之前生成的题目的hashset
String dirPath = dir+name;// 文件夹路径
Calendar cal = Calendar.getInstance();
int year = cal.get(Calendar.YEAR);//获取年份
int month=cal.get(Calendar.MONTH);//获取月份
int day=cal.get(Calendar.DATE);//获取日
int hour=cal.get(Calendar.HOUR);//小时
int minute=cal.get(Calendar.MINUTE);//分
int second=cal.get(Calendar.SECOND);//秒
String path=dirPath+"\\"+String.valueOf(year)+"-"+String.valueOf(month)+"-"+String.valueOf(day)+"-"+String.valueOf(hour)+"-"+String.valueOf(minute)+"-"+String.valueOf(second)+".txt";//设置文件名
try {
FileWriter fw = new FileWriter(path,true);//设置追加属性,不断对文件进行追加
for(int i=0;i<num;i++){//生成对应数量题目
String problem=getProblem(state);//生成相应状态的题目
if(!past.contains(problem)){//如果已有的题目中不包含该题目
problem=String.valueOf(i+1)+"."+problem;//加上序号
fw.write(problem+"\n"+"\n");//追加至问价尾
}
}
System.out.println("卷子生成完毕");
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}