考试中我们需要根据学生的状态判断是否需要抽题,如果考生状态为已抽题,说明学生为二次登陆,我们联合各个表查出学生对应的大题记录即可;如果考生状态为已交卷,说明学生考试完毕,我们直接提示学生该科已作答完毕即可,如果学生状态为未抽题,这时候又分为两种情况,如果该科考试对应的是试卷的话,我们直接从库中联合几个表查出相应的试卷即可,其过程跟二次登陆类似,比较复杂的是模板抽题,模板提供的就是一套规则,如模板告诉我们试卷上有填空题和选择题两个大题,这两个大题各有10个小题,我们需要根据模板的规则去数据库找相应的题目。
首先我们来看一下模板抽题的大概实现逻辑
上图中画出了模板抽题的主要步骤,下面我们结合代码一起来分析一下。
/**
* 随机抽题算法
*
* @param examPaperModel 试卷信息model
* @param mainList 某课程题干信息list
* @param chapterList 章节信息list
* @return
*/
上图是抽题方法的传入参数,试卷信息是指模板上要求的各种信息。包括试卷的总分数、大题个数、小题个数、每个题的最高难度等级等。mainList中包含某课程下题库中的所有题干信息、chapterList中包含该试卷下所有的章节信息,如果该试卷共包含多少张的内容,每章的比例等。
//region 初始化数据:章节分布、题型等级分布、题干大小
//在试卷中获得题型集合
List<ExamTemPaperQuestionTypeModel> typeModelList = examPaperModel.getPaperQuestionTypeList();
//region 为试卷上每个大题以及小题分配空间
//声明变量,用于存放试卷上的所有题干
ExamQuestionMainModel[][] typeMain;
//分配试卷上每个大题的存储空间
typeMain = new ExamQuestionMainModel[typeModelList.size()][];
//分配试卷上每个题型下每个小题的存储空间
for (int i = 0; i < typeModelList.size(); i++) {
typeMain[i] = new ExamQuestionMainModel[typeModelList.get(i).getQuestionTypeNum()];
}
//endregion
//region 为试卷上每个大题中小题的难度等级分配空间
//声明变量,存放每个大题的难度等级,并分配空间
int[][] typeDegrees = new int[typeModelList.size()][];
//声明变量,用于存放每个大题的所有小题的难度等级
int[][] tempTypeDegrees = new int[typeModelList.size()][];
for (int i = 0; i < typeModelList.size(); i++) {
//该大题所有小题的最高难度等级
int maxDegree = typeModelList.get(i).getQuestionMaxDegree();
//该大题期望分值
double questionHopeScore = typeModelList.get(i).getQuestionTypeScore() * (examPaperModel.getHopeScore() / examPaperModel.getScore());
//该大题总分数
double questionScore = typeModelList.get(i).getQuestionTypeScore();
//该大题的所有小题个数
int questionNum = typeModelList.get(i).getQuestionTypeNum();
//返回每个难度等级下有几道题
int[] degrees = scoreDistributionService.distribution(maxDegree, questionHopeScore, questionScore, questionNum);
//保存该题型下所有小题的等级分布
typeDegrees[i] = degrees;
//临时变量,用于保存已经抽取的题的难度等级,初始值赋值为0
tempTypeDegrees[i] = degrees;
for (int j = 0; j < tempTypeDegrees[i].length; j++) {
tempTypeDegrees[i][j] = 0;
}
}
//endregion
//region 为每章分配内存空间,并填充每章所对应的分数
//获取试卷的总分数
double paperScore = examPaperModel.getScore();
//声明变量,用于存放每个章节的分数,并分配空间
double[] chapterScore = new double[chapterList.size()];
//声明变量,用于存放已选题每个章节的分数,并分配空间
double[] tempChapterScore = new double[chapterList.size()];
for (int i = 0; i < chapterList.size(); i++) {
//填充每个章节的分数
chapterScore[i] = chapterList.get(i).getChapterRatio() * paperScore;
//已选题,初始值为0
tempChapterScore[i] = 0;
}
//endregion
//endregion
//遍历试卷上每个大题
for (int i = 0; i < typeModelList.size(); i++) {
//region 得到题库中该题型的所有题干集合
//用于存放符合条件的类型的题干集合(如选择题的所有题干)
List<ExamQuestionMainModel> typeMainList = new ArrayList<>();
//获取该课程下某类大题的所有题干
for (int j = 0; j < mainList.size(); j++) {
if (typeModelList.get(i).getQuestionTypeId().equals(mainList.get(j).getQuestionTypeId())) {
typeMainList.add(mainList.get(j));
}
}
//endregion
上面代码得作用是定义存放章节信息、题型等级信息、题干信息的变量,并分配存储空间,上面的每行代码我基本都写了注释,这里不再赘述,我要重点说一下的是在等级分布中调用了distribution方法,这个方法是老师写的方法,传入参数是该题型最高难度等级、该题型期望分值、该题型总分数、该题型小题个数,返回值为一个数组,保存的是每个难度等级下小题的个数。
//region 得到题库中该题型的所有题干集合
//用于存放符合条件的类型的题干集合(如选择题的所有题干)
List<ExamQuestionMainModel> typeMainList = new ArrayList<>();
//获取该课程下某类大题的所有题干
for (int j = 0; j < mainList.size(); j++) {
if (typeModelList.get(i).getQuestionTypeId().equals(mainList.get(j).getQuestionTypeId())) {
typeMainList.add(mainList.get(j));
}
}
//endregion
得到对应题型的题干集合。根据获取题干的数量与试卷上某大题中所有小题数量对比,执行不同方法,下面我们重点说我们随机抽题的代码。
//产生随机数,用于作为所抽题的索引
int typeMainIndex = random.nextInt(typeMainList.size());
Set<Integer> randomcollct = new HashSet<>();
//利用set结合的特性,防止产生的随机数有重复的
boolean flag = randomcollct.add(typeMainIndex);
if (flag == false) {
j--;
continue;
}
产生不重复的随机数,用于随机抽取题干list中的题目。
//根据抽取的 typeMainIndex 索引进行判断
ExamQuestionMainModel examMain = typeMainList.get(typeMainIndex);
//region 等级分布条件判断
//获得当前题干的难度等级
int degree = examMain.getDegree();
//定义变量用于存放某难度等级的题型个数
int tempTypeDegreeNumber = tempTypeDegrees[i][degree] + examMain.getBlankCount();
//当前题型个数与试卷要求的题型个数对比
if (tempTypeDegreeNumber > typeDegrees[i][degree]) {
//说明该难度等级的题目已抽取完毕
j--;
continue;
}
//endregion
判断等级分布是否符合条件。
//region 章节比例条件判断
String mainChapter = examMain.getChapter();//获取题干章节
//定义章节索引变量
int chapterIndex = -1;
//遍历所有章节
for (int k = 0; k < chapterList.size(); k++) {
//获得本章节在章节集合中的索引
if (chapterList.get(k).getChapter().equals(mainChapter)) {
chapterIndex = k;
break;
}
}
//计算平均每个题的分数
double typeMainScore = typeMainList.get(typeMainIndex).getScore() / typeModelList.get(typeMainIndex).getQuestionTypeNum();
//计算该小题实际的分数(该题有两个空则按两个小题计算)
double mainScore = typeMainScore * examMain.getBlankCount();
//定义变量并赋值(本章节已选题的分数+该小题分数)
double chapterMainScore = tempChapterScore[chapterIndex] + mainScore;
//判断现在该分数是否超过该章要求的总分数
if (chapterMainScore > chapterScore[chapterIndex]) {
//说明该章节的题目已经抽取完毕
j--;
continue;
}
//endregion
判断章节分布是否满足。
//保存已选题个数
tempTypeDegrees[i][degree] = tempTypeDegreeNumber;
//保存已选题本章节累计总分数
tempChapterScore[chapterIndex] = chapterMainScore;
//将题干保存到二维数组中
typeMain[i][j] = examMain;
如果章节分布和等级分布都满足,则保存该题。
上面是根据模板抽卷的整体逻辑,但是并不是完整的代码,我只是写了一些主要的代码。如果在以后的测试中发现问题,再来继续更新。